Files
icehrm/core/robo/robo.phar
2020-05-23 20:01:53 +02:00

79312 lines
2.4 MiB
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env php
<?php
Phar::mapPhar();
/**
* If we're running from phar load the phar autoload,
* else let the script 'robo' search for the autoloader.
*/
// Hack: \Phar::running() cannot be used reliably here to determine
// if we are running as a phar or not (works when phar is built with
// box, but does not work when phar is built with the Robo phar task.)
// We will use __FILE__ to determine our phar path; however, we cannot
// distinguish whether a __FILE__ of "/path/robo" is this file, or a
// 'robo.phar' that has been renamed to 'robo'. We will use the file
// size to differentiate.
// Recommendation: Use box to build your phar. See https://github.com/g1a/starter
$isPhar = (filesize(__FILE__) > 500000);
// Non-phar autoloader paths
$candidates = [
__DIR__.'/vendor/autoload.php',
__DIR__.'/../../autoload.php',
];
// Use our phar alias path
if ($isPhar) {
array_unshift($candidates, 'phar://robo.phar/vendor/autoload.php');
}
$autoloaderPath = false;
foreach ($candidates as $candidate) {
if (file_exists($candidate)) {
$autoloaderPath = $candidate;
break;
}
}
if (!$autoloaderPath) {
die("Could not find autoloader. Run 'composer install'.");
}
$classLoader = require $autoloaderPath;
$configFilePath = getenv('ROBO_CONFIG') ?: getenv('HOME') . '/.robo/robo.yml';
$runner = new \Robo\Runner();
$runner
->setRelativePluginNamespace('Robo\Plugin')
->setSelfUpdateRepository('consolidation/robo')
->setConfigurationFilename($configFilePath)
->setEnvConfigPrefix('ROBO')
->setClassLoader($classLoader);
$statusCode = $runner->execute($_SERVER['argv']);
exit($statusCode);
__HALT_COMPILER(); ?>
›±? robo.pharvendor/autoload.php²¼jL^²9ɤ-vendor/pear/console_getopt/Console/Getopt.phpC5¼jL^C5þ¨0A¤-vendor/pear/pear_exception/PEAR/Exception.php#<¼jL^#<ôÁ£¤'vendor/pear/archive_tar/Archive/Tar.php†P¼jL^†P”¤5vendor/pear/pear-core-minimal/src/PEAR/ErrorStack.php¼jL^V ¤0vendor/pear/pear-core-minimal/src/PEAR/Error.phpC¼jL^C×;_ð¤,vendor/pear/pear-core-minimal/src/System.php˜P¼jL^˜P¸ƒø¸¤*vendor/pear/pear-core-minimal/src/PEAR.php_<70>¼jL^_<> '"¤.vendor/pear/pear-core-minimal/src/OS/Guess.php*¼jL^*ÔÛú¤#vendor/natxet/cssmin/src/CssMin.php»t¼jL^»t.cÃö¤Wvendor/container-interop/container-interop/src/Interop/Container/ContainerInterface.phpb¼jL^bB¹Ê÷¤avendor/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.phpd¼jL^d$3@¤`vendor/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.phpY¼jL^Y Ì®&¤'vendor/composer/autoload_namespaces.php¡¼jL^¡ÏüÚû¤!vendor/composer/include_paths.php7¼jL^7mebY¤vendor/composer/ClassLoader.php“4¼jL^“4²z<C2B2>¤!vendor/composer/autoload_psr4.php޼jL^ŽJWΤ%vendor/composer/autoload_classmap.php
¼jL^
'*%h¤#vendor/composer/autoload_static.phpoQ¼jL^oQ…ˆó¤!vendor/composer/autoload_real.php
¼jL^
’žæÍ¤"vendor/composer/autoload_files.php³¼jL^³8¿Y¤*vendor/patchwork/jsqueeze/src/JSqueeze.php§¼jL^§’#(àŠ¤3vendor/league/container/src/ContainerAwareTrait.phpa¼jL^a¦SV¤)vendor/league/container/src/Container.phpÎ!¼jL^Î!ˤ4vendor/league/container/src/Argument/RawArgument.php¼jL^3—¤>vendor/league/container/src/Argument/ArgumentResolverTrait.phpS¼jL^S¿6Ì«¤Bvendor/league/container/src/Argument/ArgumentResolverInterface.phpϼjL^Ït¤=vendor/league/container/src/Argument/RawArgumentInterface.phpɼjL^ÉÓ}ì¤;vendor/league/container/src/ImmutableContainerInterface.php¹¼jL^¹+;<3B>@vendor/league/container/src/ImmutableContainerAwareInterface.phpâ¼jL^â¾@ŒM¤<vendor/league/container/src/Inflector/InflectorAggregate.php£¼jL^£v¤Evendor/league/container/src/Inflector/InflectorAggregateInterface.phpH¼jL^Há\¤3vendor/league/container/src/Inflector/Inflector.php+ ¼jL^+ ÉtK¥¤Pvendor/league/container/src/ServiceProvider/AbstractSignatureServiceProvider.php?¼jL^?-ûBY¤Qvendor/league/container/src/ServiceProvider/ServiceProviderAggregateInterface.php ¼jL^ óoÆ¿¤Gvendor/league/container/src/ServiceProvider/AbstractServiceProvider.phpî¼jL^îûHvendor/league/container/src/ServiceProvider/ServiceProviderAggregate.phpœ¼jL^œáo´¤Qvendor/league/container/src/ServiceProvider/SignatureServiceProviderInterface.phpl¼jL^lž„Pvendor/league/container/src/ServiceProvider/BootableServiceProviderInterface.phpl¼jL^l=ê¤Hvendor/league/container/src/ServiceProvider/ServiceProviderInterface.phpϼjL^ÏÞùï·¤2vendor/league/container/src/ContainerInterface.php\¼jL^\²µã¤=vendor/league/container/src/Definition/CallableDefinition.php¥¼jL^¥²áy¤Evendor/league/container/src/Definition/DefinitionFactoryInterface.php޼jL^ŽñO6F¤<vendor/league/container/src/Definition/DefinitionFactory.php¼jL^b;F€¤:vendor/league/container/src/Definition/ClassDefinition.php×¼jL^×Súß»¤Cvendor/league/container/src/Definition/ClassDefinitionInterface.phpè¼jL^è›Ó|¤>vendor/league/container/src/Definition/DefinitionInterface.phpC¼jL^CB<>=vendor/league/container/src/Definition/AbstractDefinition.php{¼jL^{üì6^¤<vendor/league/container/src/ImmutableContainerAwareTrait.phpļjL^Äygߤ3vendor/league/container/src/ReflectionContainer.php ¼jL^ °•é¤;vendor/league/container/src/Exception/NotFoundException.php¼jL^CSY¤7vendor/league/container/src/ContainerAwareInterface.php€¼jL^€©Þ¿¤Jvendor/dflydev/dot-access-data/src/Dflydev/DotAccessData/DataInterface.phpͼjL^Í\=!^¤Avendor/dflydev/dot-access-data/src/Dflydev/DotAccessData/Data.phpç¼jL^ç®2ÔK¤Avendor/dflydev/dot-access-data/src/Dflydev/DotAccessData/Util.php?¼jL^?¢.Eܤ5vendor/grasmash/expander/src/StringifierInterface.php/¼jL^/^#§¤,vendor/grasmash/expander/src/Stringifier.php­¼jL^­Êf Ť)vendor/grasmash/expander/src/Expander.phpï#¼jL^ï#<00> .vendor/grasmash/yaml-expander/src/Expander.php+"¼jL^+"Y­ˆï¤7vendor/psr/container/src/NotFoundExceptionInterface.php¼jL^Ý-‰õ¤/vendor/psr/container/src/ContainerInterface.phpJ¼jL^J"x<>é¤8vendor/psr/container/src/ContainerExceptionInterface.phpø¼jL^øN>K€¤/vendor/psr/log/Psr/Log/LoggerAwareInterface.php)¼jL^)Èj ±¤#vendor/psr/log/Psr/Log/LogLevel.phpP¼jL^P<00>òº¤+vendor/psr/log/Psr/Log/LoggerAwareTrait.php<68>¼jL^<5E>z%Ô¤3vendor/psr/log/Psr/Log/InvalidArgumentException.php`¼jL^` ˆX1¤%vendor/psr/log/Psr/Log/NullLogger.phpüjL^ÃÑâÕI¤*vendor/psr/log/Psr/Log/LoggerInterface.php ¼jL^ à¢d?¤&vendor/psr/log/Psr/Log/LoggerTrait.phpW ¼jL^W ¹Wåj¤)vendor/psr/log/Psr/Log/AbstractLogger.php ¼jL^ šGl¤'vendor/symfony/polyfill-php73/Php73.phpg¼jL^g/¼Æn¤?vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php¼jL^<F¤+vendor/symfony/polyfill-php73/bootstrap.php¼jL^`Öø<C396>¤+vendor/symfony/polyfill-ctype/bootstrap.phpS¼jL^S½ñ'vendor/symfony/polyfill-ctype/Ctype.php}¼jL^}\Šè°¤<vendor/symfony/event-dispatcher/EventDispatcherInterface.phpº
¼jL^º
Šç¤)vendor/symfony/event-dispatcher/Event.phpž¼jL^ž»Má¤>vendor/symfony/event-dispatcher/LegacyEventDispatcherProxy.php¼jL^íÍ3vendor/symfony/event-dispatcher/EventDispatcher.php *¼jL^ *Èp<C388><vendor/symfony/event-dispatcher/EventSubscriberInterface.phpì¼jL^ìç[µ@¤<vendor/symfony/event-dispatcher/ImmutableEventDispatcher.phpÌ ¼jL^Ì Ñ=פ4vendor/symfony/event-dispatcher/LegacyEventProxy.php ¼jL^ A?½¤0vendor/symfony/event-dispatcher/GenericEvent.phpr¼jL^rñáÔ®¤Mvendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php,¼jL^,ÄÆö¤Kvendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php¼jL^_<>ê¤Bvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.phpÆ4¼jL^Æ4n" “¤9vendor/symfony/event-dispatcher/Debug/WrappedListener.php¼jL^ÑÉf¤Kvendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php…¼jL^…)bȤ(vendor/symfony/filesystem/Filesystem.phpqs¼jL^qs>¼±d¤<vendor/symfony/filesystem/Exception/IOExceptionInterface.php¦¼jL^¦¨™i”¤:vendor/symfony/filesystem/Exception/ExceptionInterface.phpмjL^Ð nßj¤3vendor/symfony/filesystem/Exception/IOException.php¼¼jL^¼HDú¤@vendor/symfony/filesystem/Exception/InvalidArgumentException.phpϼjL^ÏÐ*“Ф=vendor/symfony/filesystem/Exception/FileNotFoundException.phpɼjL^É ´¤Fvendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.phpù¼jL^ù¢Ë¾¤3vendor/symfony/event-dispatcher-contracts/Event.php& ¼jL^& ^£ÈÁ¤%vendor/symfony/finder/SplFileInfo.php¼jL^ƒL5vendor/symfony/finder/Comparator/NumberComparator.php
¼jL^
¹¢¤/vendor/symfony/finder/Comparator/Comparator.php¼jL^~4<>ؤ3vendor/symfony/finder/Comparator/DateComparator.php¥¼jL^¥Ûis¤ vendor/symfony/finder/Finder.php½Z¼jL^½ZX¤vendor/symfony/finder/Glob.phpí¼jL^홾²{¤<vendor/symfony/finder/Iterator/FilecontentFilterIterator.php¦¼jL^¦rÜ~¤=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php{ ¼jL^{ ÌÓ¹¤:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.phpƒ¼jL^ƒŠ=̤9vendor/symfony/finder/Iterator/FilenameFilterIterator.php•¼jL^• ËpÀ¤:vendor/symfony/finder/Iterator/DateRangeFilterIterator.php¦¼jL^¦ƒ­ò)¤7vendor/symfony/finder/Iterator/CustomFilterIterator.phpÙ¼jL^Ùd=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.phpÿ¼jL^ÿ ²c¤Avendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php» ¼jL^» »ñza¤3vendor/symfony/finder/Iterator/SortableIterator.phpÙ ¼jL^Ù @Ë™X¤;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.phpÓ¼jL^Ó¿h÷¤9vendor/symfony/finder/Iterator/FileTypeFilterIterator.phpC¼jL^CQN¶å¤5vendor/symfony/finder/Iterator/PathFilterIterator.php«¼jL^«}£¬¤#vendor/symfony/finder/Gitignore.php{ ¼jL^{ òNm¤9vendor/symfony/finder/Exception/AccessDeniedException.php«¼jL^«ÊcWÞ¤>vendor/symfony/finder/Exception/DirectoryNotFoundException.php£¼jL^£ßRvendor/symfony/yaml/Parser.php¯¾¼jL^¯¾1“ÎŒ¤vendor/symfony/yaml/Escaper.php¼jL^Sx]¤!vendor/symfony/yaml/Unescaper.php"¼jL^"Ô°ò¤vendor/symfony/yaml/Dumper.phpT¼jL^T™„‡¤vendor/symfony/yaml/Inline.phpôx¼jL^ôxª8v¤vendor/symfony/yaml/Yaml.php° ¼jL^° 3JU¤+vendor/symfony/yaml/Command/LintCommand.php;"¼jL^;"È!£¤/vendor/symfony/yaml/Exception/DumpException.phpǼjL^ÇŒ¤4vendor/symfony/yaml/Exception/ExceptionInterface.phpμjL^ÎB9›¤2vendor/symfony/yaml/Exception/RuntimeException.phpå¼jL^åô_q¦¤0vendor/symfony/yaml/Exception/ParseException.phpt ¼jL^t ÃS ¤'vendor/symfony/yaml/Tag/TaggedValue.phpżjL^ÅnÃß%¤?vendor/symfony/service-contracts/ServiceSubscriberInterface.phpƼjL^ÆSRôܤ3vendor/symfony/service-contracts/ResetInterface.phpç¼jL^çµvú¤=vendor/symfony/service-contracts/ServiceProviderInterface.phpº¼jL^º­mX¤;vendor/symfony/service-contracts/ServiceSubscriberTrait.phpä¼jL^äú¤8vendor/symfony/service-contracts/ServiceLocatorTrait.phpÅ ¼jL^Å ñß.­¤+vendor/symfony/process/ExecutableFinder.php
¼jL^
.ly;¤*vendor/symfony/process/Pipes/UnixPipes.phpQ¼jL^Q<00>±Ö+¤-vendor/symfony/process/Pipes/WindowsPipes.php(¼jL^(}.vendor/symfony/process/Pipes/AbstractPipes.php}¼jL^}™&/vendor/symfony/process/Pipes/PipesInterface.phpѼjL^Ѱ6<C2B0>¤&vendor/symfony/process/InputStream.php ¼jL^ !×uɤ%vendor/symfony/process/PhpProcess.phpÌ ¼jL^Ì £L>!¤.vendor/symfony/process/PhpExecutableFinder.phpZ
¼jL^Z
) À¤=vendor/symfony/process/Exception/ProcessSignaledException.php±¼jL^±¾°äò¤3vendor/symfony/process/Exception/LogicException.phpÒ¼jL^Ò°¨W¤7vendor/symfony/process/Exception/ExceptionInterface.php¼jL^ÂÑçÝ+¤;vendor/symfony/process/Exception/ProcessFailedException.php<68>¼jL^<5E>´5vendor/symfony/process/Exception/RuntimeException.phpá¼jL^á>H™¤=vendor/symfony/process/Exception/ProcessTimedOutException.php¼jL^ûµ"¤=vendor/symfony/process/Exception/InvalidArgumentException.phpð¼jL^ðË…<C38B>ë¤'vendor/symfony/process/ProcessUtils.phpD¼jL^Dyeë¤"vendor/symfony/process/Process.phpϼjL^‚Ï<00>¤î¤(vendor/symfony/console/ConsoleEvents.php,¼jL^,~”HG¤&vendor/symfony/console/Application.php[¯¼jL^[¯*F¢Õ¤/vendor/symfony/console/Logger/ConsoleLogger.php¼jL^ÂAp3¤9vendor/symfony/console/Input/StreamableInputInterface.phpi¼jL^iõ™í¤+vendor/symfony/console/Input/ArrayInput.phpg¼jL^gÏ‹†¤*vendor/symfony/console/Input/ArgvInput.php,¼jL^,šyjq¤.vendor/symfony/console/Input/InputArgument.phpN ¼jL^N 4tÿÁ¤4vendor/symfony/console/Input/InputAwareInterface.php:¼jL^:± '´¤,vendor/symfony/console/Input/InputOption.php¡¼jL^¡àĤ0vendor/symfony/console/Input/InputDefinition.phpS+¼jL^S+ÌC•¤,vendor/symfony/console/Input/StringInput.phpò¼jL^ò*šY
¤&vendor/symfony/console/Input/Input.phpr¼jL^rUjx¤/vendor/symfony/console/Input/InputInterface.php±¼jL^±õΈڤ#vendor/symfony/console/Terminal.php¼jL^³ó4ç¤4vendor/symfony/console/Resources/bin/hiddeninput.exe$¼jL^$<>¥v¤8vendor/symfony/console/Output/ConsoleOutputInterface.php4¼jL^4}ò¤0vendor/symfony/console/Output/BufferedOutput.phpH¼jL^H 9¿Ô¤1vendor/symfony/console/Output/OutputInterface.php$ ¼jL^$ Âw;¤(vendor/symfony/console/Output/Output.php´¼jL^´<00>\j¤6vendor/symfony/console/Output/ConsoleSectionOutput.phpD¼jL^D˜ƒ¤/vendor/symfony/console/Output/ConsoleOutput.phpؼjL^Øÿ™¤,vendor/symfony/console/Output/NullOutput.phpn¼jL^nt<>D»¤.vendor/symfony/console/Output/StreamOutput.phpɼjL^É­`¤,vendor/symfony/console/Style/OutputStyle.phpý ¼jL^ý 1kz¤-vendor/symfony/console/Style/SymfonyStyle.phpÂ7¼jL^Â7 ¤/vendor/symfony/console/Style/StyleInterface.php( ¼jL^( ¯5 ñ¤2vendor/symfony/console/Question/ChoiceQuestion.php¬¼jL^¬Ü8|'¤,vendor/symfony/console/Question/Question.php²¼jL^²*ñÖͤ8vendor/symfony/console/Question/ConfirmationQuestion.php¼jL^?uôȤ9vendor/symfony/console/Formatter/OutputFormatterStyle.php¦¼jL^¦!SF¡¤Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php<¼jL^<Ÿ½ÐZ¤=vendor/symfony/console/Formatter/OutputFormatterInterface.phpH¼jL^H¦°ñ¤4vendor/symfony/console/Formatter/OutputFormatter.phpP¼jL^P`ˆ‰Ò¤Fvendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php¬¼jL^¬`ÇK>¤>vendor/symfony/console/Formatter/OutputFormatterStyleStack.phpý ¼jL^ý ;2ü¤?vendor/symfony/console/CommandLoader/CommandLoaderInterface.php ¼jL^ ºÇö¤?vendor/symfony/console/CommandLoader/ContainerCommandLoader.phpÔ¼jL^Ô@«ˆ×¤=vendor/symfony/console/CommandLoader/FactoryCommandLoader.php7¼jL^7m_ò¤4vendor/symfony/console/Descriptor/TextDescriptor.php31¼jL^31g(ê¤<vendor/symfony/console/Descriptor/ApplicationDescription.phpÕ¼jL^ÕP¡V¤3vendor/symfony/console/Descriptor/XmlDescriptor.php6#¼jL^6#!pôP¤9vendor/symfony/console/Descriptor/DescriptorInterface.phpƒ¼jL^ƒPÚZö¤0vendor/symfony/console/Descriptor/Descriptor.phpd ¼jL^d ¡ÖåT¤4vendor/symfony/console/Descriptor/JsonDescriptor.php˜¼jL^˜«Å+¤8vendor/symfony/console/Descriptor/MarkdownDescriptor.phpżjL^Å0vendor/symfony/console/Command/LockableTrait.phpž¼jL^žܫǒ¤.vendor/symfony/console/Command/HelpCommand.phpR ¼jL^R ÝÈP¤.vendor/symfony/console/Command/ListCommand.phpþ ¼jL^þ šy3ë¤*vendor/symfony/console/Command/Command.phpñK¼jL^ñKœh¬¤,vendor/symfony/console/Helper/TableStyle.php¾<¼jL^¾<r…£<E280A6>¤+vendor/symfony/console/Helper/TableRows.phpU¼jL^U$Öÿ¤7vendor/symfony/console/Helper/SymfonyQuestionHelper.phpy ¼jL^y øOº¤1vendor/symfony/console/Helper/HelperInterface.phpp¼jL^pàñn¤+vendor/symfony/console/Helper/TableCell.phpüjL^ÃB¶Ý%¤/vendor/symfony/console/Helper/ProcessHelper.php«¼jL^«fØÈ¤0vendor/symfony/console/Helper/TableSeparator.php¼jL^&Šª ¤1vendor/symfony/console/Helper/FormatterHelper.phpá
¼jL^á
Uúö¤-vendor/symfony/console/Helper/ProgressBar.phpÂE¼jL^ÂE|¨ïÛ¤6vendor/symfony/console/Helper/DebugFormatterHelper.phpð¼jL^ðΉI¤+vendor/symfony/console/Helper/HelperSet.php} ¼jL^} |¤(vendor/symfony/console/Helper/Dumper.phpè¼jL^è+'šf¤2vendor/symfony/console/Helper/InputAwareHelper.phpë¼jL^ë¼<>¤0vendor/symfony/console/Helper/QuestionHelper.phpT>¼jL^T>Ž*Ïî¤(vendor/symfony/console/Helper/Helper.php°¼jL^°}Ku·¤'vendor/symfony/console/Helper/Table.phpij¼jL^ijPʤ3vendor/symfony/console/Helper/ProgressIndicator.phpt¼jL^tPT¶û¤2vendor/symfony/console/Helper/DescriptorHelper.php ¼jL^ Ëûþ¤Dvendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php)¼jL^)ò 8~¤3vendor/symfony/console/Exception/LogicException.phpª¼jL^ªSML<4D>¤;vendor/symfony/console/Exception/InvalidOptionException.phpü¼jL^üí“;¤7vendor/symfony/console/Exception/ExceptionInterface.php©¼jL^©âló¸¤5vendor/symfony/console/Exception/RuntimeException.php®¼jL^®¶*b¤?vendor/symfony/console/Exception/NamespaceNotFoundException.phpÚ¼jL^ÚBL»H¤=vendor/symfony/console/Exception/InvalidArgumentException.php¾¼jL^¾îu i¤=vendor/symfony/console/Exception/CommandNotFoundException.phpȼjL^ÈN‹¤6vendor/symfony/console/Event/ConsoleTerminateEvent.phpñ¼jL^ñ¶o™¤2vendor/symfony/console/Event/ConsoleErrorEvent.phpó¼jL^óªÄa”¤4vendor/symfony/console/Event/ConsoleCommandEvent.phpD¼jL^DB¦TC¤-vendor/symfony/console/Event/ConsoleEvent.phpļjL^ÄxS*¤6vendor/symfony/console/EventListener/ErrorListener.phpý
¼jL^ý
sþ¤@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.phpI¼jL^Iæ(~¤Fvendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php9¼jL^9>|zK¤@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.phpþI¼jL^þIÒÖë¤-vendor/symfony/polyfill-mbstring/Mbstring.phpñm¼jL^ñm<14>û'¤.vendor/symfony/polyfill-mbstring/bootstrap.php¼jL^Ÿ•‘¤4vendor/consolidation/config/src/ConfigAwareTrait.php.¼jL^.ôåù ¤Dvendor/consolidation/config/src/Util/ConfigInterpolatorInterface.phpÿ¼jL^ÿ$ÍÛT¤6vendor/consolidation/config/src/Util/ConfigOverlay.phpR¼jL^Rš‘*p¤4vendor/consolidation/config/src/Util/ConfigGroup.phpǼjL^ÇØ»K¤4vendor/consolidation/config/src/Util/ConfigMerge.php!¼jL^!:ϳ²¤2vendor/consolidation/config/src/Util/ArrayUtil.phpÊ ¼jL^Ê hæ(Û¤@vendor/consolidation/config/src/Util/ConfigInterpolatorTrait.phpv¼jL^vå—ç¤?vendor/consolidation/config/src/Util/ConfigRuntimeInterface.php¼jL^Žo.:¤5vendor/consolidation/config/src/Util/Interpolator.php_ ¼jL^_ ç ƒ¤7vendor/consolidation/config/src/Util/ConfigFallback.phpn¼jL^n¦;äG¤2vendor/consolidation/config/src/Util/EnvConfig.php¼jL^Ì‘ÌϤ;vendor/consolidation/config/src/Inject/ConfigForSetters.php¢¼jL^¢p_И¤;vendor/consolidation/config/src/Inject/ConfigForCommand.phpA¼jL^A9¤3vendor/consolidation/config/src/ConfigInterface.phpã ¼jL^ã Ô;”¤*vendor/consolidation/config/src/Config.php¦ ¼jL^¦ Z÷ìÆ¤@vendor/consolidation/config/src/Loader/ConfigLoaderInterface.php?¼jL^?gt¤7vendor/consolidation/config/src/Loader/ConfigLoader.phpS¼jL^SFÉ?õ¤:vendor/consolidation/config/src/Loader/ConfigProcessor.phpx¼jL^x<00> ¤;vendor/consolidation/config/src/Loader/YamlConfigLoader.phpy¼jL^y˜Å¤Fvendor/consolidation/config/src/GlobalOptionDefaultValuesInterface.phpÞ¼jL^ÞD¸È8¤8vendor/consolidation/config/src/ConfigAwareInterface.phpt¼jL^t Áb=¤;vendor/consolidation/annotated-command/src/CommandError.phpP¼jL^P­Œ9¾¤?vendor/consolidation/annotated-command/src/CommandProcessor.php<68>#¼jL^<5E>#`§¤Ovendor/consolidation/annotated-command/src/Options/AlterOptionsCommandEvent.php¢ ¼jL^¢ ФXvendor/consolidation/annotated-command/src/Options/AutomaticOptionsProviderInterface.phpm¼jL^m<00>I™f¤Gvendor/consolidation/annotated-command/src/Options/PrepareFormatter.php¼jL^Ô<>Rq¤Qvendor/consolidation/annotated-command/src/Options/PrepareTerminalWidthOption.phpá ¼jL^á rªÆî¤=vendor/consolidation/annotated-command/src/AnnotationData.php¼jL^Ît4q¤Avendor/consolidation/annotated-command/src/Cache/CacheWrapper.php¼jL^Ó'¶¤Ivendor/consolidation/annotated-command/src/Cache/SimpleCacheInterface.phpc¼jL^c ë<¤>vendor/consolidation/annotated-command/src/Cache/NullCache.php¼jL^pqÙ-¤@vendor/consolidation/annotated-command/src/ExitCodeInterface.php ¼jL^ ûÖð+¤:vendor/consolidation/annotated-command/src/CommandData.php©¼jL^©ª¥M¤Hvendor/consolidation/annotated-command/src/Input/StdinAwareInterface.phpǼjL^Ç?IÄ>¤Dvendor/consolidation/annotated-command/src/Input/StdinAwareTrait.php"¼jL^";Èb·¤Avendor/consolidation/annotated-command/src/Input/StdinHandler.php8¼jL^8פ;vendor/consolidation/annotated-command/src/ResultWriter.php¼jL^+ʉ¤Cvendor/consolidation/annotated-command/src/CommandFileDiscovery.php;¼jL^;ˆV¤Ovendor/consolidation/annotated-command/src/CommandCreationListenerInterface.php¯¼jL^¯"\4¤@vendor/consolidation/annotated-command/src/ParameterInjector.phpܼjL^Üöa{ú¤Jvendor/consolidation/annotated-command/src/CommandInfoAltererInterface.phpé¼jL^éÑ<>·”¤Jvendor/consolidation/annotated-command/src/Parser/Internal/DocblockTag.php¾¼jL^¾ï,Wvendor/consolidation/annotated-command/src/Parser/Internal/FullyQualifiedClassCache.php ¼jL^ ænJ¤Tvendor/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php .¼jL^ .Da5q¤Gvendor/consolidation/annotated-command/src/Parser/Internal/CsvUtils.php^¼jL^^<O·¤Ivendor/consolidation/annotated-command/src/Parser/Internal/TagFactory.php¨¼jL^¨ÂM7¤[vendor/consolidation/annotated-command/src/Parser/Internal/CommandDocBlockParserFactory.php/¼jL^/Ñ<><C391>¤Kvendor/consolidation/annotated-command/src/Parser/CommandInfoSerializer.php ¼jL^ çÿ°²¤Mvendor/consolidation/annotated-command/src/Parser/CommandInfoDeserializer.phpƒ ¼jL^ƒ ÄÁ–¤Avendor/consolidation/annotated-command/src/Parser/CommandInfo.phpqX¼jL^qXØó<00>¤Nvendor/consolidation/annotated-command/src/Parser/DefaultsWithDescriptions.phpm¼jL^mÅü¼d¤Avendor/consolidation/annotated-command/src/ParameterInjection.phpɼjL^ÉÚ1ù¤?vendor/consolidation/annotated-command/src/AnnotatedCommand.php-¼jL^-Ÿ¹[&¤Kvendor/consolidation/annotated-command/src/Hooks/ProcessResultInterface.php<68>¼jL^<5E>ê2Un¤Hvendor/consolidation/annotated-command/src/Hooks/OptionHookInterface.phpмjL^ŠÈük<C3BC>¤Kvendor/consolidation/annotated-command/src/Hooks/ExtractOutputInterface.phph¼jL^hîȉë¤Nvendor/consolidation/annotated-command/src/Hooks/StatusDeterminerInterface.phpY¼jL^Y£M^¤Ivendor/consolidation/annotated-command/src/Hooks/AlterResultInterface.php#¼jL^#~<7E>«u¤@vendor/consolidation/annotated-command/src/Hooks/HookManager.phpž7¼jL^ž7ìXn¤[vendor/consolidation/annotated-command/src/Hooks/Dispatchers/CommandEventHookDispatcher.php¼jL^ Ÿ¤Xvendor/consolidation/annotated-command/src/Hooks/Dispatchers/ExtracterHookDispatcher.phpý¼jL^ýþfÜá¤Ovendor/consolidation/annotated-command/src/Hooks/Dispatchers/HookDispatcher.phpa¼jL^a7<Yvendor/consolidation/annotated-command/src/Hooks/Dispatchers/InitializeHookDispatcher.phpé¼jL^é%Ì€¿¤Vvendor/consolidation/annotated-command/src/Hooks/Dispatchers/OptionsHookDispatcher.phpüjL^ÃÌ.Á¤Wvendor/consolidation/annotated-command/src/Hooks/Dispatchers/ValidateHookDispatcher.phpµ¼jL^µ?ÎÆ¤Wvendor/consolidation/annotated-command/src/Hooks/Dispatchers/InteractHookDispatcher.php¼jL^õ™‘¤\vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ProcessResultHookDispatcher.php¼jL^A ÷¤]vendor/consolidation/annotated-command/src/Hooks/Dispatchers/ReplaceCommandHookDispatcher.phpš¼jL^š‰!_vendor/consolidation/annotated-command/src/Hooks/Dispatchers/StatusDeterminerHookDispatcher.phpP¼jL^PÝÖG¤Gvendor/consolidation/annotated-command/src/Hooks/ValidatorInterface.php#¼jL^#¿ýwP¤Hvendor/consolidation/annotated-command/src/Hooks/InteractorInterface.php:¼jL^:·Ã<C2B7>ð¤Lvendor/consolidation/annotated-command/src/Hooks/InitializeHookInterface.php´¼jL^´gó¤Bvendor/consolidation/annotated-command/src/OutputDataInterface.php?¼jL^?<05>¼¤Fvendor/consolidation/annotated-command/src/CommandCreationListener.phpƒ¼jL^ƒ[¤Kvendor/consolidation/annotated-command/src/Events/CustomEventAwareTrait.php\¼jL^\§6s'¤Ovendor/consolidation/annotated-command/src/Events/CustomEventAwareInterface.php ¼jL^ %“Ct¤<vendor/consolidation/annotated-command/src/CommandResult.phpæ¼jL^æð:üW¤Fvendor/consolidation/annotated-command/src/AnnotatedCommandFactory.phpA¼jL^A0˜8?¤@vendor/consolidation/annotated-command/src/Help/HelpDocument.php˜¼jL^˜ÿ€¥ï¤Evendor/consolidation/annotated-command/src/Help/HelpDocumentAlter.phpˆ¼jL^ˆŽUÖ¤?vendor/consolidation/annotated-command/src/Help/HelpCommand.php_¼jL^_<00>šèÿ¤Gvendor/consolidation/annotated-command/src/Help/HelpDocumentBuilder.php“ ¼jL^“ `»“¤¤Ovendor/consolidation/output-formatters/src/Options/OverrideOptionsInterface.php¼jL^OkÂt¤Gvendor/consolidation/output-formatters/src/Options/FormatterOptions.phpf,¼jL^f,ßorB¤Ovendor/consolidation/output-formatters/src/StructuredData/ListDataInterface.phpƼjL^ƈ'1"¤Wvendor/consolidation/output-formatters/src/StructuredData/RenderCellCollectionTrait.phpH¼jL^Hëà¡ý¤Pvendor/consolidation/output-formatters/src/StructuredData/TableDataInterface.php…¼jL^…Å‹»¤Jvendor/consolidation/output-formatters/src/StructuredData/HelpDocument.phpE¼jL^E(T/¤Svendor/consolidation/output-formatters/src/StructuredData/OriginalDataInterface.php¼jL^ð…a§¤Nvendor/consolidation/output-formatters/src/StructuredData/UnstructuredData.php3¼jL^3÷xGޤJvendor/consolidation/output-formatters/src/StructuredData/RowsOfFields.php,¼jL^,ãRvendor/consolidation/output-formatters/src/StructuredData/RestructureInterface.php<68>¼jL^<5E>¹ðc{¤Tvendor/consolidation/output-formatters/src/StructuredData/AbstractStructuredList.php2¼jL^2¾ õǤNvendor/consolidation/output-formatters/src/StructuredData/ListDataFromKeys.php¤¼jL^¤”gs­¤Rvendor/consolidation/output-formatters/src/StructuredData/UnstructuredListData.php?¼jL^?w*ZC¤Svendor/consolidation/output-formatters/src/StructuredData/UnstructuredInterface.php©¼jL^©è<ѤJvendor/consolidation/output-formatters/src/StructuredData/PropertyList.phpp ¼jL^p ³÷3£¤Tvendor/consolidation/output-formatters/src/StructuredData/Xml/XmlSchemaInterface.php¼jL^MÀ}¤Rvendor/consolidation/output-formatters/src/StructuredData/Xml/DomDataInterface.phpá¼jL^á¹Ö²„¤Kvendor/consolidation/output-formatters/src/StructuredData/Xml/XmlSchema.phpC¼jL^CÊ7:¤Qvendor/consolidation/output-formatters/src/StructuredData/ConversionInterface.php¼jL^Q•;¤Uvendor/consolidation/output-formatters/src/StructuredData/MetadataHolderInterface.phpüjL^ÃÁö¤Qvendor/consolidation/output-formatters/src/StructuredData/RenderCellInterface.phpÞ¼jL^Þ¬p<}¤Nvendor/consolidation/output-formatters/src/StructuredData/CallableRenderer.phpJ¼jL^Jzº(¤Nvendor/consolidation/output-formatters/src/StructuredData/AbstractListData.php2¼jL^2‚¤[vendor/consolidation/output-formatters/src/StructuredData/RenderCellCollectionInterface.phpà¼jL^à%SœV¤Vvendor/consolidation/output-formatters/src/StructuredData/RowsOfFieldsWithMetadata.phpüjL^ÃÿQV4¤Qvendor/consolidation/output-formatters/src/StructuredData/NumericCellRenderer.php
¼jL^
ØŽ©¤Mvendor/consolidation/output-formatters/src/StructuredData/AssociativeList.php®¼jL^®Ï/¿¤Qvendor/consolidation/output-formatters/src/StructuredData/MetadataHolderTrait.php;
¼jL^;
4“¤Q¤Ovendor/consolidation/output-formatters/src/StructuredData/MetadataInterface.php¼jL^RËÇq¤Lvendor/consolidation/output-formatters/src/StructuredData/FieldProcessor.phpñ¼jL^ñÃh8<68>¤Kvendor/consolidation/output-formatters/src/Validate/ValidationInterface.php¼jL^žÓU†¤Kvendor/consolidation/output-formatters/src/Validate/ValidDataTypesTrait.php§¼jL^§º<E28093>Ovendor/consolidation/output-formatters/src/Validate/ValidDataTypesInterface.phpj¼jL^j 9P¦¤\vendor/consolidation/output-formatters/src/Transformations/UnstructuredDataFieldAccessor.php‡¼jL^‡À.ž¢¤Mvendor/consolidation/output-formatters/src/Transformations/PropertyParser.php¼jL^5€Èù¤Svendor/consolidation/output-formatters/src/Transformations/Wrap/CalculateWidths.php¼jL^;U>¤Pvendor/consolidation/output-formatters/src/Transformations/Wrap/ColumnWidths.php¢¼jL^¢$¯¤^vendor/consolidation/output-formatters/src/Transformations/PropertyListTableTransformation.phpù¼jL^ùc‡¤Rvendor/consolidation/output-formatters/src/Transformations/TableTransformation.phpѼjL^ÑÔ«9V¤]vendor/consolidation/output-formatters/src/Transformations/UnstructuredDataTransformation.phpF¼jL^F{ØMà¤avendor/consolidation/output-formatters/src/Transformations/UnstructuredDataListTransformation.phpb¼jL^bØpö ¤Lvendor/consolidation/output-formatters/src/Transformations/ReorderFields.php
¼jL^
µóSvendor/consolidation/output-formatters/src/Transformations/DomToArraySimplifier.php¥¼jL^¥¬ma ¤Jvendor/consolidation/output-formatters/src/Transformations/WordWrapper.php¼jL^</t¤\vendor/consolidation/output-formatters/src/Transformations/StringTransformationInterface.phpl¼jL^l5 JR¤[vendor/consolidation/output-formatters/src/Transformations/OverrideRestructureInterface.php¼jL^·ñ …¤Wvendor/consolidation/output-formatters/src/Transformations/SimplifyToArrayInterface.phpżjL^Å2yé¤Gvendor/consolidation/output-formatters/src/Formatters/ListFormatter.php&¼jL^&YÀiɤMvendor/consolidation/output-formatters/src/Formatters/FormatterAwareTrait.phpë¼jL^ëkÕ\â¤Tvendor/consolidation/output-formatters/src/Formatters/MetadataFormatterInterface.php5¼jL^5òܨ¤Kvendor/consolidation/output-formatters/src/Formatters/SectionsFormatter.phpÞ ¼jL^Þ •vÞ¤Gvendor/consolidation/output-formatters/src/Formatters/JsonFormatter.php¼jL^šxÆ»¤Lvendor/consolidation/output-formatters/src/Formatters/FormatterInterface.php=¼jL^=çºMo¤Fvendor/consolidation/output-formatters/src/Formatters/CsvFormatter.phpˆ¼jL^ˆœé¤Jvendor/consolidation/output-formatters/src/Formatters/VarDumpFormatter.phpY¼jL^YªOö¤Qvendor/consolidation/output-formatters/src/Formatters/FormatterAwareInterface.phpî¼jL^î
¤Lvendor/consolidation/output-formatters/src/Formatters/SerializeFormatter.phpå¼jL^åô¿³¸¤Ivendor/consolidation/output-formatters/src/Formatters/StringFormatter.php4¼jL^4frë¤Nvendor/consolidation/output-formatters/src/Formatters/RenderTableDataTrait.php¥¼jL^¥.Õ—¤Lvendor/consolidation/output-formatters/src/Formatters/VarExportFormatter.phpî¼jL^î<00>îÖ¤Gvendor/consolidation/output-formatters/src/Formatters/YamlFormatter.phpC¼jL^CG<>ĤKvendor/consolidation/output-formatters/src/Formatters/NoOutputFormatter.php¼jL^àâw$¤Hvendor/consolidation/output-formatters/src/Formatters/TableFormatter.phpu¼jL^uúH¾G¤Pvendor/consolidation/output-formatters/src/Formatters/MetadataFormatterTrait.phpM¼jL^M¸®H¿¤Fvendor/consolidation/output-formatters/src/Formatters/XmlFormatter.php¡ ¼jL^¡ þ Ivendor/consolidation/output-formatters/src/Formatters/PrintRFormatter.phpâ¼jL^â=k<>Mvendor/consolidation/output-formatters/src/Formatters/RenderDataInterface.php>¼jL^><00>TzÛ¤Fvendor/consolidation/output-formatters/src/Formatters/TsvFormatter.php¼jL^ëÄ–þ¤Mvendor/consolidation/output-formatters/src/Formatters/HumanReadableFormat.php¨¼jL^¨5ý[%¤?vendor/consolidation/output-formatters/src/FormatterManager.phpçH¼jL^çH7ûñŒ¤Ovendor/consolidation/output-formatters/src/Exception/UnknownFormatException.phpV¼jL^V¤Tvendor/consolidation/output-formatters/src/Exception/AbstractDataFormatException.phpܼjL^Ü<00>œWP¤Rvendor/consolidation/output-formatters/src/Exception/IncompatibleDataException.phpô¼jL^ô»å¤Ovendor/consolidation/output-formatters/src/Exception/InvalidFormatException.php9¼jL^9ôÕø¤Nvendor/consolidation/output-formatters/src/Exception/UnknownFieldException.phpP¼jL^PÛÖY¤:vendor/consolidation/self-update/src/SelfUpdateCommand.php.¼jL^.|¶¤'vendor/consolidation/log/src/Logger.php!$¼jL^!$¦³¾L¤8vendor/consolidation/log/src/StylableLoggerInterface.php¼¼jL^¼kÒ¤9vendor/consolidation/log/src/LogOutputStylerInterface.phpG¼jL^G‡Pt¤0vendor/consolidation/log/src/ConsoleLogLevel.phpù¼jL^ùnô'x¤7vendor/consolidation/log/src/SymfonyLogOutputStyler.php¼jL^2Âö§¤0vendor/consolidation/log/src/LogOutputStyler.phpE¼jL^E®k¤8vendor/consolidation/log/src/UnstyledLogOutputStyler.php ¼jL^ Ó=­™¤.vendor/consolidation/log/src/LoggerManager.php ¼jL^ /C'&¤src/TaskAccessor.php¼jL^—:ì¤src/Application.php ¼jL^ %0 {¤"src/GlobalOptionsEventListener.php¦¼jL^¦B+ 'src/Contract/OutputAdapterInterface.php¼jL^»‡J±¤"src/Contract/RollbackInterface.php”¼jL^”ä#src/Contract/SimulatedInterface.php¢¼jL^¢‡FGg¤0src/Contract/ProgressIndicatorAwareInterface.phpT¼jL^T™Óò<C393>¤&src/Contract/BuilderAwareInterface.php¨¼jL^¨àA¡¤"src/Contract/ProgressInterface.php+¼jL^+ë9Á¤,src/Contract/VerbosityThresholdInterface.phpb¼jL^b±§©Ì¤!src/Contract/IOAwareInterface.phpé¼jL^é®5i
¤!src/Contract/CommandInterface.php¾¼jL^¾:j¤!src/Contract/PrintedInterface.phpï¼jL^ïIC3 ¤%src/Contract/WrappedTaskInterface.php¶¼jL^¶»ïP̤%src/Contract/OutputAwareInterface.php{¼jL^{
[jo¤$src/Contract/CompletionInterface.php{¼jL^{§%ï=¤%src/Contract/ConfigAwareInterface.phpw¼jL^w<00><><EFBFBD>src/Contract/TaskInterface.php(¼jL^(Ô6^I¤$src/Contract/InflectionInterface.phpI¼jL^I1ñ;“¤src/Collection/Element.php•
¼jL^•
sVé¤src/Collection/Temporary.phpî¼jL^î<00>Έ¤(src/Collection/CollectionProcessHook.phpi¼jL^i;%-+¤src/Collection/loadTasks.php~¼jL^~Z W^¤src/Collection/Collection.phpKb¼jL^Kb­áî¤$src/Collection/CompletionWrapper.phpÓ ¼jL^Ó O»8y¤&src/Collection/CollectionInterface.phpç¼jL^çц½‘¤src/Collection/CallableTask.phpH¼jL^H®ÌIÔ¤$src/Collection/CollectionBuilder.phpM¼jL^Mìz—¤,src/Collection/NestedCollectionInterface.php¼jL^Sôdx¤src/Collection/TaskForEach.phpá¼jL^áá7ÊD¤src/TaskInfo.php¼jL^?†T„¤src/Config/Config.phpk¼jL^kh5è¤1src/Config/GlobalOptionDefaultValuesInterface.php¼jL^Úa"e¤ src/Tasks.php=¼jL^=àG┤src/Result.php¼jL^nJCe¤ src/Robo.phpƒ>¼jL^ƒ>²ï}¤!src/State/StateAwareInterface.php@¼jL^@<00>pÙã¤src/State/Consumer.phpª¼jL^ªu!)¤src/State/StateAwareTrait.phpî¼jL^î5Ú(A¤src/State/Data.php… ¼jL^… "¢¤src/Config.phpt¼jL^tûÖsrc/Common/TimeKeeper.php¼jL^À,‰ü¤src/Common/OutputAwareTrait.php<68>¼jL^<5E>æ¤ ¤src/Common/InflectionTrait.php»¼jL^»h䌤src/Common/ConfigAwareTrait.php
¼jL^
ŽA¿e¤src/Common/IO.php¼jL^È¢aޤsrc/Common/DynamicParams.php3¼jL^3™ñ6Ó¤src/Common/ExecTrait.php™(¼jL^™(Æ –¤ src/Common/BuilderAwareTrait.phpi¼jL^iï°—v¤src/Common/OutputAdapter.phps¼jL^s6Èo$¤src/Common/CommandArguments.php2 ¼jL^2 /+´src/Common/InputAwareTrait.php ¼jL^ eÆåç¤'src/Common/ResourceExistenceChecker.phpY ¼jL^Y Ìצ¤src/Common/Timer.php„¼jL^„A;\®¤src/Common/ExecOneCommand.phpç¼jL^ç<1C><>À¤src/Common/ProcessExecutor.phpï¼jL^ï±2c¤src/Common/CommandReceiver.php¼jL^ð ߤ&src/Common/VerbosityThresholdTrait.phpr ¼jL^r ӃȤ*src/Common/ProgressIndicatorAwareTrait.phpÜ ¼jL^Ü j)”¤ src/Common/ProgressIndicator.phpU¼jL^U¦D%¤src/Common/TaskIO.php5¼jL^5 ÕV•¤src/Common/ProcessUtils.phpÁ
¼jL^Á
Ætm”¤src/Common/ExecCommand.phpî¼jL^î@¾v<C2BE>¤src/Task/Docker/Stop.phpƼjL^ÆÎsrc/Task/Docker/Build.phpP¼jL^PiðP¤src/Task/Docker/Result.php>¼jL^>æQ</¤src/Task/Docker/Commit.phpϼjL^ÏàBȤsrc/Task/Docker/Run.php̼jL^̺]ޤsrc/Task/Docker/Base.php%¼jL^%;,”¤src/Task/Docker/Remove.phpè¼jL^èsq ¤src/Task/Docker/Exec.phpç¼jL^çîSʱ¤src/Task/Docker/loadTasks.php`¼jL^`jÞ·-¤src/Task/Docker/Pull.phpä¼jL^ä»!Фsrc/Task/Docker/Start.phpʼjL^ÊŸ¯Æà¤src/Task/Archive/Extract.phpn#¼jL^n#í™<C3AD>ͤsrc/Task/Archive/loadTasks.php¼jL^»Åî$¤src/Task/Archive/Pack.php~ ¼jL^~ 6U·ö¤src/Task/Gulp/Run.php;¼jL^;@ä.'¤src/Task/Gulp/Base.php¼jL^ðþ³®¤src/Task/Gulp/loadTasks.phpf¼jL^f<00>ºI¡¤src/Task/CommandStack.php¼jL^gÇLÁ¤src/Task/Bower/Update.phpb¼jL^b1@ÊÆ¤src/Task/Bower/Install.php«¼jL^«­»FŒ¤src/Task/Bower/Base.phpüjL^ÃA`¤src/Task/Bower/loadTasks.phpM¼jL^MÊu8€¤src/Task/File/TmpFile.php¼jL^ ²l(¤src/Task/File/Replace.php¾ ¼jL^¾ ¦"Ùê¤src/Task/File/Concat.phpn¼jL^n˜êñu¤src/Task/File/loadTasks.php¹¼jL^¹0ÓêÙ¤src/Task/File/Write.php¼jL^?úf¤"src/Task/Development/PhpServer.php¼jL^¿¦“¤src/Task/Development/GitHub.phpN ¼jL^N Øc¤!src/Task/Development/PackPhar.php©¼jL^©}nöÖ¤$src/Task/Development/OpenBrowser.php¼¼jL^¼ƒË¤"src/Task/Development/loadTasks.phpª¼jL^ªž©ž¤&src/Task/Development/GitHubRelease.php¼jL^+‰ä¤,src/Task/Development/GenerateMarkdownDoc.phpuP¼jL^uPêFÆô¤src/Task/Development/SemVer.php¼jL^Nÿò¤"src/Task/Development/Changelog.php"¼jL^"o†¤%src/Task/Development/GenerateTask.phpd¼jL^dýåÁ¶¤src/Task/Composer/Update.php!¼jL^!5…¤src/Task/Composer/Init.php³ ¼jL^³ g Ý©¤src/Task/Composer/Install.php)¼jL^)$6wV¤'src/Task/Composer/RequireDependency.phpl¼jL^là{"!¤src/Task/Composer/Config.phpö¼jL^öú#src/Task/Composer/CreateProject.php
¼jL^
§”7¶¤"src/Task/Composer/DumpAutoload.php!¼jL^!Åì¹B¤src/Task/Composer/Base.php¶¼jL^¶Ul¨ú¤src/Task/Composer/Remove.php×¼jL^׿²H¤src/Task/Composer/loadTasks.php[
¼jL^[
ªn¾®¤src/Task/Composer/Validate.phpñ¼jL^ñÂ!src/Task/Filesystem/MirrorDir.phpi¼jL^iË=¤"src/Task/Filesystem/FlattenDir.phpè ¼jL^è q Íʤsrc/Task/Filesystem/BaseDir.php2¼jL^2z@B¤!src/Task/Filesystem/DeleteDir.phpå¼jL^ålE﨤%src/Task/Filesystem/loadShortcuts.php ¼jL^ >C:†¤ src/Task/Filesystem/CleanDir.php1¼jL^1Á2ød¤src/Task/Filesystem/CopyDir.php'¼jL^'™½¤!src/Task/Filesystem/loadTasks.php|¼jL^|ȵ¤src/Task/Filesystem/TmpDir.phpw¼jL^wØôݤsrc/Task/Filesystem/WorkDir.php¼jL^oå·¤'src/Task/Filesystem/FilesystemStack.php5¼jL^50Ewó¤src/Task/Testing/Phpspec.php” ¼jL^” {+¬Î¤src/Task/Testing/Behat.phpÅ ¼jL^Å ÁÖ^Q¤src/Task/Testing/Atoum.php£ ¼jL^£ 5Í"¤src/Task/Testing/PHPUnit.phpø¼jL^øä“jž¤src/Task/Testing/Codecept.phpK¼jL^Km nL¤src/Task/Testing/loadTasks.php¼jL^¸src/Task/ApiGen/loadTasks.php@¼jL^@\[t¤src/Task/ApiGen/ApiGen.phpŠ3¼jL^Š3ñý2Ô¤src/Task/Vcs/HgStack.phpñ ¼jL^ñ ¿Ðsrc/Task/Vcs/loadShortcuts.phpu¼jL^usrc/Task/Vcs/SvnStack.phpƼjL^ÆÏ|Áë¤src/Task/Vcs/loadTasks.phpž¼jL^ž#Þú]¤src/Task/Vcs/GitStack.phpo¼jL^o<00>w
¤src/Task/StackBasedTask.php»¼jL^»Ð}NN¤src/Task/Assets/Scss.phpu ¼jL^u ü¡ç±¤src/Task/Assets/ImageMinify.php­T¼jL^­TÅóË‚¤#src/Task/Assets/CssPreprocessor.phpǼjL^ljzsrc/Task/Assets/Minify.php³¼jL^³|kV:¤src/Task/Assets/Less.php| ¼jL^| w7:}¤src/Task/Assets/loadTasks.phpܼjL^Ü®ìA¤src/Task/BaseTask.php ¼jL^ •â¥o¤src/Task/Npm/Update.phpW¼jL^W ÅaA¤src/Task/Npm/Install.phpŸ¼jL^Ÿ$•í¦¤src/Task/Npm/Base.phpc¼jL^csrc/Task/Npm/loadTasks.php7¼jL^7!h
¿¤src/Task/Base/Watch.php­ ¼jL^­ z«@Ĥsrc/Task/Base/ExecStack.phpj¼jL^j´êÿa¤src/Task/Base/ParallelExec.php}¼jL^}$6ºÅ¤src/Task/Base/loadShortcuts.php4¼jL^4RÓžX¤src/Task/Base/Exec.phpç
¼jL^ç
¢Â”Ÿ¤src/Task/Base/loadTasks.php¡¼jL^¡Hwá<¤ src/Task/Base/SymfonyCommand.phpñ¼jL^ñФsrc/Task/Simulator.php޼jL^Ž£ù¤­¤src/Task/Remote/Ssh.phpó¼jL^óâÖG0¤src/Task/Remote/loadTasks.php¼jL^.ò…Ö¤src/Task/Remote/Rsync.php!¼jL^!‡Ì5¤-src/ClassDiscovery/AbstractClassDiscovery.php·¼jL^·ò<>.src/ClassDiscovery/ClassDiscoveryInterface.phpé¼jL^ébn<>¤1src/ClassDiscovery/RelativeNamespaceDiscovery.phpg
¼jL^g
V¿î¤src/ResultData.php
¼jL^
4Ç«U¤src/LoadAllTasks.php¼jL^Güxp¤src/Log/ResultPrinter.phpG ¼jL^G ˜ó™¤src/Log/RoboLogLevel.phpî¼jL^îYˆ
¶¤src/Log/RoboLogStyle.phpv¼jL^v…û$¤src/Log/RoboLogger.phpç¼jL^çB{Ô¤$src/Symfony/SymfonyStyleInjector.phpȼjL^È…I
¤%src/Exception/AbortTasksException.phpp¼jL^pŠÞÈk¤src/Exception/TaskException.php™¼jL^™#src/Exception/TaskExitException.phpͼjL^Ͷˆç¤src/Runner.php«G¼jL^«G¼ž{<7B>¤robo^¼jL^^¢ß ¤<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit1040fe349fd04fbe8683abc5aae8aae1::getLoader();
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
/**
* PHP Version 5
*
* Copyright (c) 2001-2015, The PEAR developers
*
* This source file is subject to the BSD-2-Clause license,
* that is bundled with this package in the file LICENSE, and is
* available through the world-wide-web at the following url:
* http://opensource.org/licenses/bsd-license.php.
*
* @category Console
* @package Console_Getopt
* @author Andrei Zmievski <andrei@php.net>
* @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause
* @version CVS: $Id$
* @link http://pear.php.net/package/Console_Getopt
*/
require_once 'PEAR.php';
/**
* Command-line options parsing class.
*
* @category Console
* @package Console_Getopt
* @author Andrei Zmievski <andrei@php.net>
* @license http://opensource.org/licenses/bsd-license.php BSD-2-Clause
* @link http://pear.php.net/package/Console_Getopt
*/
class Console_Getopt
{
/**
* Parses the command-line options.
*
* The first parameter to this function should be the list of command-line
* arguments without the leading reference to the running program.
*
* The second parameter is a string of allowed short options. Each of the
* option letters can be followed by a colon ':' to specify that the option
* requires an argument, or a double colon '::' to specify that the option
* takes an optional argument.
*
* The third argument is an optional array of allowed long options. The
* leading '--' should not be included in the option name. Options that
* require an argument should be followed by '=', and options that take an
* option argument should be followed by '=='.
*
* The return value is an array of two elements: the list of parsed
* options and the list of non-option command-line arguments. Each entry in
* the list of parsed options is a pair of elements - the first one
* specifies the option, and the second one specifies the option argument,
* if there was one.
*
* Long and short options can be mixed.
*
* Most of the semantics of this function are based on GNU getopt_long().
*
* @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options
* @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
*
* @return array two-element array containing the list of parsed options and
* the non-option arguments
*/
public static function getopt2($args, $short_options, $long_options = null, $skip_unknown = false)
{
return Console_Getopt::doGetopt(2, $args, $short_options, $long_options, $skip_unknown);
}
/**
* This function expects $args to start with the script name (POSIX-style).
* Preserved for backwards compatibility.
*
* @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options
*
* @see getopt2()
* @return array two-element array containing the list of parsed options and
* the non-option arguments
*/
public static function getopt($args, $short_options, $long_options = null, $skip_unknown = false)
{
return Console_Getopt::doGetopt(1, $args, $short_options, $long_options, $skip_unknown);
}
/**
* The actual implementation of the argument parsing code.
*
* @param int $version Version to use
* @param array $args an array of command-line arguments
* @param string $short_options specifies the list of allowed short options
* @param array $long_options specifies the list of allowed long options
* @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
*
* @return array
*/
public static function doGetopt($version, $args, $short_options, $long_options = null, $skip_unknown = false)
{
// in case you pass directly readPHPArgv() as the first arg
if (PEAR::isError($args)) {
return $args;
}
if (empty($args)) {
return array(array(), array());
}
$non_opts = $opts = array();
settype($args, 'array');
if ($long_options) {
sort($long_options);
}
/*
* Preserve backwards compatibility with callers that relied on
* erroneous POSIX fix.
*/
if ($version < 2) {
if (isset($args[0][0]) && $args[0][0] != '-') {
array_shift($args);
}
}
for ($i = 0; $i < count($args); $i++) {
$arg = $args[$i];
/* The special element '--' means explicit end of
options. Treat the rest of the arguments as non-options
and end the loop. */
if ($arg == '--') {
$non_opts = array_merge($non_opts, array_slice($args, $i + 1));
break;
}
if ($arg[0] != '-' || (strlen($arg) > 1 && $arg[1] == '-' && !$long_options)) {
$non_opts = array_merge($non_opts, array_slice($args, $i));
break;
} elseif (strlen($arg) > 1 && $arg[1] == '-') {
$error = Console_Getopt::_parseLongOption(substr($arg, 2),
$long_options,
$opts,
$i,
$args,
$skip_unknown);
if (PEAR::isError($error)) {
return $error;
}
} elseif ($arg == '-') {
// - is stdin
$non_opts = array_merge($non_opts, array_slice($args, $i));
break;
} else {
$error = Console_Getopt::_parseShortOption(substr($arg, 1),
$short_options,
$opts,
$i,
$args,
$skip_unknown);
if (PEAR::isError($error)) {
return $error;
}
}
}
return array($opts, $non_opts);
}
/**
* Parse short option
*
* @param string $arg Argument
* @param string[] $short_options Available short options
* @param string[][] &$opts
* @param int &$argIdx
* @param string[] $args
* @param boolean $skip_unknown suppresses Console_Getopt: unrecognized option
*
* @return void
*/
protected static function _parseShortOption($arg, $short_options, &$opts, &$argIdx, $args, $skip_unknown)
{
for ($i = 0; $i < strlen($arg); $i++) {
$opt = $arg[$i];
$opt_arg = null;
/* Try to find the short option in the specifier string. */
if (($spec = strstr($short_options, $opt)) === false || $arg[$i] == ':') {
if ($skip_unknown === true) {
break;
}
$msg = "Console_Getopt: unrecognized option -- $opt";
return PEAR::raiseError($msg);
}
if (strlen($spec) > 1 && $spec[1] == ':') {
if (strlen($spec) > 2 && $spec[2] == ':') {
if ($i + 1 < strlen($arg)) {
/* Option takes an optional argument. Use the remainder of
the arg string if there is anything left. */
$opts[] = array($opt, substr($arg, $i + 1));
break;
}
} else {
/* Option requires an argument. Use the remainder of the arg
string if there is anything left. */
if ($i + 1 < strlen($arg)) {
$opts[] = array($opt, substr($arg, $i + 1));
break;
} else if (isset($args[++$argIdx])) {
$opt_arg = $args[$argIdx];
/* Else use the next argument. */;
if (Console_Getopt::_isShortOpt($opt_arg)
|| Console_Getopt::_isLongOpt($opt_arg)) {
$msg = "option requires an argument --$opt";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
} else {
$msg = "option requires an argument --$opt";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
}
}
$opts[] = array($opt, $opt_arg);
}
}
/**
* Checks if an argument is a short option
*
* @param string $arg Argument to check
*
* @return bool
*/
protected static function _isShortOpt($arg)
{
return strlen($arg) == 2 && $arg[0] == '-'
&& preg_match('/[a-zA-Z]/', $arg[1]);
}
/**
* Checks if an argument is a long option
*
* @param string $arg Argument to check
*
* @return bool
*/
protected static function _isLongOpt($arg)
{
return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' &&
preg_match('/[a-zA-Z]+$/', substr($arg, 2));
}
/**
* Parse long option
*
* @param string $arg Argument
* @param string[] $long_options Available long options
* @param string[][] &$opts
* @param int &$argIdx
* @param string[] $args
*
* @return void|PEAR_Error
*/
protected static function _parseLongOption($arg, $long_options, &$opts, &$argIdx, $args, $skip_unknown)
{
@list($opt, $opt_arg) = explode('=', $arg, 2);
$opt_len = strlen($opt);
for ($i = 0; $i < count($long_options); $i++) {
$long_opt = $long_options[$i];
$opt_start = substr($long_opt, 0, $opt_len);
$long_opt_name = str_replace('=', '', $long_opt);
/* Option doesn't match. Go on to the next one. */
if ($long_opt_name != $opt) {
continue;
}
$opt_rest = substr($long_opt, $opt_len);
/* Check that the options uniquely matches one of the allowed
options. */
if ($i + 1 < count($long_options)) {
$next_option_rest = substr($long_options[$i + 1], $opt_len);
} else {
$next_option_rest = '';
}
if ($opt_rest != '' && $opt[0] != '=' &&
$i + 1 < count($long_options) &&
$opt == substr($long_options[$i+1], 0, $opt_len) &&
$next_option_rest != '' &&
$next_option_rest[0] != '=') {
$msg = "Console_Getopt: option --$opt is ambiguous";
return PEAR::raiseError($msg);
}
if (substr($long_opt, -1) == '=') {
if (substr($long_opt, -2) != '==') {
/* Long option requires an argument.
Take the next argument if one wasn't specified. */;
if (!strlen($opt_arg)) {
if (!isset($args[++$argIdx])) {
$msg = "Console_Getopt: option requires an argument --$opt";
return PEAR::raiseError($msg);
}
$opt_arg = $args[$argIdx];
}
if (Console_Getopt::_isShortOpt($opt_arg)
|| Console_Getopt::_isLongOpt($opt_arg)) {
$msg = "Console_Getopt: option requires an argument --$opt";
return PEAR::raiseError($msg);
}
}
} else if ($opt_arg) {
$msg = "Console_Getopt: option --$opt doesn't allow an argument";
return PEAR::raiseError($msg);
}
$opts[] = array('--' . $opt, $opt_arg);
return;
}
if ($skip_unknown === true) {
return;
}
return PEAR::raiseError("Console_Getopt: unrecognized option --$opt");
}
/**
* Safely read the $argv PHP array across different PHP configurations.
* Will take care on register_globals and register_argc_argv ini directives
*
* @return mixed the $argv PHP array or PEAR error if not registered
*/
public static function readPHPArgv()
{
global $argv;
if (!is_array($argv)) {
if (!@is_array($_SERVER['argv'])) {
if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
$msg = "Could not read cmd args (register_argc_argv=Off?)";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
return $GLOBALS['HTTP_SERVER_VARS']['argv'];
}
return $_SERVER['argv'];
}
return $argv;
}
}
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
/**
* PEAR_Exception
*
* PHP version 5
*
* @category PEAR
* @package PEAR_Exception
* @author Tomas V. V. Cox <cox@idecnet.com>
* @author Hans Lellelid <hans@velum.net>
* @author Bertrand Mansion <bmansion@mamasam.com>
* @author Greg Beaver <cellog@php.net>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR_Exception
* @since File available since Release 1.0.0
*/
/**
* Base PEAR_Exception Class
*
* 1) Features:
*
* - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception))
* - Definable triggers, shot when exceptions occur
* - Pretty and informative error messages
* - Added more context info available (like class, method or cause)
* - cause can be a PEAR_Exception or an array of mixed
* PEAR_Exceptions/PEAR_ErrorStack warnings
* - callbacks for specific exception classes and their children
*
* 2) Ideas:
*
* - Maybe a way to define a 'template' for the output
*
* 3) Inherited properties from PHP Exception Class:
*
* protected $message
* protected $code
* protected $line
* protected $file
* private $trace
*
* 4) Inherited methods from PHP Exception Class:
*
* __clone
* __construct
* getMessage
* getCode
* getFile
* getLine
* getTraceSafe
* getTraceSafeAsString
* __toString
*
* 5) Usage example
*
* <code>
* require_once 'PEAR/Exception.php';
*
* class Test {
* function foo() {
* throw new PEAR_Exception('Error Message', ERROR_CODE);
* }
* }
*
* function myLogger($pear_exception) {
* echo $pear_exception->getMessage();
* }
* // each time a exception is thrown the 'myLogger' will be called
* // (its use is completely optional)
* PEAR_Exception::addObserver('myLogger');
* $test = new Test;
* try {
* $test->foo();
* } catch (PEAR_Exception $e) {
* print $e;
* }
* </code>
*
* @category PEAR
* @package PEAR_Exception
* @author Tomas V.V.Cox <cox@idecnet.com>
* @author Hans Lellelid <hans@velum.net>
* @author Bertrand Mansion <bmansion@mamasam.com>
* @author Greg Beaver <cellog@php.net>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: @package_version@
* @link http://pear.php.net/package/PEAR_Exception
* @since Class available since Release 1.0.0
*/
class PEAR_Exception extends Exception
{
const OBSERVER_PRINT = -2;
const OBSERVER_TRIGGER = -4;
const OBSERVER_DIE = -8;
protected $cause;
private static $_observers = array();
private static $_uniqueid = 0;
private $_trace;
/**
* Supported signatures:
* - PEAR_Exception(string $message);
* - PEAR_Exception(string $message, int $code);
* - PEAR_Exception(string $message, Exception $cause);
* - PEAR_Exception(string $message, Exception $cause, int $code);
* - PEAR_Exception(string $message, PEAR_Error $cause);
* - PEAR_Exception(string $message, PEAR_Error $cause, int $code);
* - PEAR_Exception(string $message, array $causes);
* - PEAR_Exception(string $message, array $causes, int $code);
*
* @param string $message exception message
* @param int|Exception|PEAR_Error|array|null $p2 exception cause
* @param int|null $p3 exception code or null
*/
public function __construct($message, $p2 = null, $p3 = null)
{
if (is_int($p2)) {
$code = $p2;
$this->cause = null;
} elseif (is_object($p2) || is_array($p2)) {
// using is_object allows both Exception and PEAR_Error
if (is_object($p2) && !($p2 instanceof Exception)) {
if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) {
throw new PEAR_Exception(
'exception cause must be Exception, ' .
'array, or PEAR_Error'
);
}
}
$code = $p3;
if (is_array($p2) && isset($p2['message'])) {
// fix potential problem of passing in a single warning
$p2 = array($p2);
}
$this->cause = $p2;
} else {
$code = null;
$this->cause = null;
}
parent::__construct($message, $code);
$this->signal();
}
/**
* Add an exception observer
*
* @param mixed $callback - A valid php callback, see php func is_callable()
* - A PEAR_Exception::OBSERVER_* constant
* - An array(const PEAR_Exception::OBSERVER_*,
* mixed $options)
* @param string $label The name of the observer. Use this if you want
* to remove it later with removeObserver()
*
* @return void
*/
public static function addObserver($callback, $label = 'default')
{
self::$_observers[$label] = $callback;
}
/**
* Remove an exception observer
*
* @param string $label Name of the observer
*
* @return void
*/
public static function removeObserver($label = 'default')
{
unset(self::$_observers[$label]);
}
/**
* Generate a unique ID for an observer
*
* @return int unique identifier for an observer
*/
public static function getUniqueId()
{
return self::$_uniqueid++;
}
/**
* Send a signal to all observers
*
* @return void
*/
protected function signal()
{
foreach (self::$_observers as $func) {
if (is_callable($func)) {
call_user_func($func, $this);
continue;
}
settype($func, 'array');
switch ($func[0]) {
case self::OBSERVER_PRINT :
$f = (isset($func[1])) ? $func[1] : '%s';
printf($f, $this->getMessage());
break;
case self::OBSERVER_TRIGGER :
$f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
trigger_error($this->getMessage(), $f);
break;
case self::OBSERVER_DIE :
$f = (isset($func[1])) ? $func[1] : '%s';
die(printf($f, $this->getMessage()));
break;
default:
trigger_error('invalid observer type', E_USER_WARNING);
}
}
}
/**
* Return specific error information that can be used for more detailed
* error messages or translation.
*
* This method may be overridden in child exception classes in order
* to add functionality not present in PEAR_Exception and is a placeholder
* to define API
*
* The returned array must be an associative array of parameter => value like so:
* <pre>
* array('name' => $name, 'context' => array(...))
* </pre>
*
* @return array
*/
public function getErrorData()
{
return array();
}
/**
* Returns the exception that caused this exception to be thrown
*
* @return Exception|array The context of the exception
*/
public function getCause()
{
return $this->cause;
}
/**
* Function must be public to call on caused exceptions
*
* @param array $causes Array that gets filled.
*
* @return void
*/
public function getCauseMessage(&$causes)
{
$trace = $this->getTraceSafe();
$cause = array('class' => get_class($this),
'message' => $this->message,
'file' => 'unknown',
'line' => 'unknown');
if (isset($trace[0])) {
if (isset($trace[0]['file'])) {
$cause['file'] = $trace[0]['file'];
$cause['line'] = $trace[0]['line'];
}
}
$causes[] = $cause;
if ($this->cause instanceof PEAR_Exception) {
$this->cause->getCauseMessage($causes);
} elseif ($this->cause instanceof Exception) {
$causes[] = array('class' => get_class($this->cause),
'message' => $this->cause->getMessage(),
'file' => $this->cause->getFile(),
'line' => $this->cause->getLine());
} elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) {
$causes[] = array('class' => get_class($this->cause),
'message' => $this->cause->getMessage(),
'file' => 'unknown',
'line' => 'unknown');
} elseif (is_array($this->cause)) {
foreach ($this->cause as $cause) {
if ($cause instanceof PEAR_Exception) {
$cause->getCauseMessage($causes);
} elseif ($cause instanceof Exception) {
$causes[] = array('class' => get_class($cause),
'message' => $cause->getMessage(),
'file' => $cause->getFile(),
'line' => $cause->getLine());
} elseif (class_exists('PEAR_Error')
&& $cause instanceof PEAR_Error
) {
$causes[] = array('class' => get_class($cause),
'message' => $cause->getMessage(),
'file' => 'unknown',
'line' => 'unknown');
} elseif (is_array($cause) && isset($cause['message'])) {
// PEAR_ErrorStack warning
$causes[] = array(
'class' => $cause['package'],
'message' => $cause['message'],
'file' => isset($cause['context']['file']) ?
$cause['context']['file'] :
'unknown',
'line' => isset($cause['context']['line']) ?
$cause['context']['line'] :
'unknown',
);
}
}
}
}
/**
* Build a backtrace and return it
*
* @return array Backtrace
*/
public function getTraceSafe()
{
if (!isset($this->_trace)) {
$this->_trace = $this->getTrace();
if (empty($this->_trace)) {
$backtrace = debug_backtrace();
$this->_trace = array($backtrace[count($backtrace)-1]);
}
}
return $this->_trace;
}
/**
* Gets the first class of the backtrace
*
* @return string Class name
*/
public function getErrorClass()
{
$trace = $this->getTraceSafe();
return $trace[0]['class'];
}
/**
* Gets the first method of the backtrace
*
* @return string Method/function name
*/
public function getErrorMethod()
{
$trace = $this->getTraceSafe();
return $trace[0]['function'];
}
/**
* Converts the exception to a string (HTML or plain text)
*
* @return string String representation
*
* @see toHtml()
* @see toText()
*/
public function __toString()
{
if (isset($_SERVER['REQUEST_URI'])) {
return $this->toHtml();
}
return $this->toText();
}
/**
* Generates a HTML representation of the exception
*
* @return string HTML code
*/
public function toHtml()
{
$trace = $this->getTraceSafe();
$causes = array();
$this->getCauseMessage($causes);
$html = '<table style="border: 1px" cellspacing="0">' . "\n";
foreach ($causes as $i => $cause) {
$html .= '<tr><td colspan="3" style="background: #ff9999">'
. str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: '
. htmlspecialchars($cause['message'])
. ' in <b>' . $cause['file'] . '</b> '
. 'on line <b>' . $cause['line'] . '</b>'
. "</td></tr>\n";
}
$html .= '<tr><td colspan="3" style="background-color: #aaaaaa; text-align: center; font-weight: bold;">Exception trace</td></tr>' . "\n"
. '<tr><td style="text-align: center; background: #cccccc; width:20px; font-weight: bold;">#</td>'
. '<td style="text-align: center; background: #cccccc; font-weight: bold;">Function</td>'
. '<td style="text-align: center; background: #cccccc; font-weight: bold;">Location</td></tr>' . "\n";
foreach ($trace as $k => $v) {
$html .= '<tr><td style="text-align: center;">' . $k . '</td>'
. '<td>';
if (!empty($v['class'])) {
$html .= $v['class'] . $v['type'];
}
$html .= $v['function'];
$args = array();
if (!empty($v['args'])) {
foreach ($v['args'] as $arg) {
if (is_null($arg)) {
$args[] = 'null';
} else if (is_array($arg)) {
$args[] = 'Array';
} else if (is_object($arg)) {
$args[] = 'Object('.get_class($arg).')';
} else if (is_bool($arg)) {
$args[] = $arg ? 'true' : 'false';
} else if (is_int($arg) || is_double($arg)) {
$args[] = $arg;
} else {
$arg = (string)$arg;
$str = htmlspecialchars(substr($arg, 0, 16));
if (strlen($arg) > 16) {
$str .= '&hellip;';
}
$args[] = "'" . $str . "'";
}
}
}
$html .= '(' . implode(', ', $args) . ')'
. '</td>'
. '<td>' . (isset($v['file']) ? $v['file'] : 'unknown')
. ':' . (isset($v['line']) ? $v['line'] : 'unknown')
. '</td></tr>' . "\n";
}
$html .= '<tr><td style="text-align: center;">' . ($k+1) . '</td>'
. '<td>{main}</td>'
. '<td>&nbsp;</td></tr>' . "\n"
. '</table>';
return $html;
}
/**
* Generates text representation of the exception and stack trace
*
* @return string
*/
public function toText()
{
$causes = array();
$this->getCauseMessage($causes);
$causeMsg = '';
foreach ($causes as $i => $cause) {
$causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': '
. $cause['message'] . ' in ' . $cause['file']
. ' on line ' . $cause['line'] . "\n";
}
return $causeMsg . $this->getTraceAsString();
}
}
?>
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* File::CSV
*
* PHP versions 4 and 5
*
* Copyright (c) 1997-2008,
* Vincent Blavet <vincent@phpconcept.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @category File_Formats
* @package Archive_Tar
* @author Vincent Blavet <vincent@phpconcept.net>
* @copyright 1997-2010 The Authors
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id$
* @link http://pear.php.net/package/Archive_Tar
*/
// If the PEAR class cannot be loaded via the autoloader,
// then try to require_once it from the PHP include path.
if (!class_exists('PEAR')) {
require_once 'PEAR.php';
}
define('ARCHIVE_TAR_ATT_SEPARATOR', 90001);
define('ARCHIVE_TAR_END_BLOCK', pack("a512", ''));
if (!function_exists('gzopen') && function_exists('gzopen64')) {
function gzopen($filename, $mode, $use_include_path = 0)
{
return gzopen64($filename, $mode, $use_include_path);
}
}
if (!function_exists('gztell') && function_exists('gztell64')) {
function gztell($zp)
{
return gztell64($zp);
}
}
if (!function_exists('gzseek') && function_exists('gzseek64')) {
function gzseek($zp, $offset, $whence = SEEK_SET)
{
return gzseek64($zp, $offset, $whence);
}
}
/**
* Creates a (compressed) Tar archive
*
* @package Archive_Tar
* @author Vincent Blavet <vincent@phpconcept.net>
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
* @version $Revision$
*/
class Archive_Tar extends PEAR
{
/**
* @var string Name of the Tar
*/
public $_tarname = '';
/**
* @var boolean if true, the Tar file will be gzipped
*/
public $_compress = false;
/**
* @var string Type of compression : 'none', 'gz', 'bz2' or 'lzma2'
*/
public $_compress_type = 'none';
/**
* @var string Explode separator
*/
public $_separator = ' ';
/**
* @var file descriptor
*/
public $_file = 0;
/**
* @var string Local Tar name of a remote Tar (http:// or ftp://)
*/
public $_temp_tarname = '';
/**
* @var string regular expression for ignoring files or directories
*/
public $_ignore_regexp = '';
/**
* @var object PEAR_Error object
*/
public $error_object = null;
/**
* Format for data extraction
*
* @var string
*/
public $_fmt = '';
/**
* @var int Length of the read buffer in bytes
*/
protected $buffer_length;
/**
* Archive_Tar Class constructor. This flavour of the constructor only
* declare a new Archive_Tar object, identifying it by the name of the
* tar file.
* If the compress argument is set the tar will be read or created as a
* gzip or bz2 compressed TAR file.
*
* @param string $p_tarname The name of the tar archive to create
* @param string $p_compress can be null, 'gz', 'bz2' or 'lzma2'. This
* parameter indicates if gzip, bz2 or lzma2 compression
* is required. For compatibility reason the
* boolean value 'true' means 'gz'.
* @param int $buffer_length Length of the read buffer in bytes
*
* @return bool
*/
public function __construct($p_tarname, $p_compress = null, $buffer_length = 512)
{
parent::__construct();
$this->_compress = false;
$this->_compress_type = 'none';
if (($p_compress === null) || ($p_compress == '')) {
if (@file_exists($p_tarname)) {
if ($fp = @fopen($p_tarname, "rb")) {
// look for gzip magic cookie
$data = fread($fp, 2);
fclose($fp);
if ($data == "\37\213") {
$this->_compress = true;
$this->_compress_type = 'gz';
// No sure it's enought for a magic code ....
} elseif ($data == "BZ") {
$this->_compress = true;
$this->_compress_type = 'bz2';
} elseif (file_get_contents($p_tarname, false, null, 1, 4) == '7zXZ') {
$this->_compress = true;
$this->_compress_type = 'lzma2';
}
}
} else {
// probably a remote file or some file accessible
// through a stream interface
if (substr($p_tarname, -2) == 'gz') {
$this->_compress = true;
$this->_compress_type = 'gz';
} elseif ((substr($p_tarname, -3) == 'bz2') ||
(substr($p_tarname, -2) == 'bz')
) {
$this->_compress = true;
$this->_compress_type = 'bz2';
} else {
if (substr($p_tarname, -2) == 'xz') {
$this->_compress = true;
$this->_compress_type = 'lzma2';
}
}
}
} else {
if (($p_compress === true) || ($p_compress == 'gz')) {
$this->_compress = true;
$this->_compress_type = 'gz';
} else {
if ($p_compress == 'bz2') {
$this->_compress = true;
$this->_compress_type = 'bz2';
} else {
if ($p_compress == 'lzma2') {
$this->_compress = true;
$this->_compress_type = 'lzma2';
} else {
$this->_error(
"Unsupported compression type '$p_compress'\n" .
"Supported types are 'gz', 'bz2' and 'lzma2'.\n"
);
return false;
}
}
}
}
$this->_tarname = $p_tarname;
if ($this->_compress) { // assert zlib or bz2 or xz extension support
if ($this->_compress_type == 'gz') {
$extname = 'zlib';
} else {
if ($this->_compress_type == 'bz2') {
$extname = 'bz2';
} else {
if ($this->_compress_type == 'lzma2') {
$extname = 'xz';
}
}
}
if (!extension_loaded($extname)) {
PEAR::loadExtension($extname);
}
if (!extension_loaded($extname)) {
$this->_error(
"The extension '$extname' couldn't be found.\n" .
"Please make sure your version of PHP was built " .
"with '$extname' support.\n"
);
return false;
}
}
if (version_compare(PHP_VERSION, "5.5.0-dev") < 0) {
$this->_fmt = "a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" .
"a8checksum/a1typeflag/a100link/a6magic/a2version/" .
"a32uname/a32gname/a8devmajor/a8devminor/a131prefix";
} else {
$this->_fmt = "Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/" .
"Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/" .
"Z32uname/Z32gname/Z8devmajor/Z8devminor/Z131prefix";
}
$this->buffer_length = $buffer_length;
}
public function __destruct()
{
$this->_close();
// ----- Look for a local copy to delete
if ($this->_temp_tarname != '') {
@unlink($this->_temp_tarname);
}
}
/**
* This method creates the archive file and add the files / directories
* that are listed in $p_filelist.
* If a file with the same name exist and is writable, it is replaced
* by the new tar.
* The method return false and a PEAR error text.
* The $p_filelist parameter can be an array of string, each string
* representing a filename or a directory name with their path if
* needed. It can also be a single string with names separated by a
* single blank.
* For each directory added in the archive, the files and
* sub-directories are also added.
* See also createModify() method for more details.
*
* @param array $p_filelist An array of filenames and directory names, or a
* single string with names separated by a single
* blank space.
*
* @return true on success, false on error.
* @see createModify()
*/
public function create($p_filelist)
{
return $this->createModify($p_filelist, '', '');
}
/**
* This method add the files / directories that are listed in $p_filelist in
* the archive. If the archive does not exist it is created.
* The method return false and a PEAR error text.
* The files and directories listed are only added at the end of the archive,
* even if a file with the same name is already archived.
* See also createModify() method for more details.
*
* @param array $p_filelist An array of filenames and directory names, or a
* single string with names separated by a single
* blank space.
*
* @return true on success, false on error.
* @see createModify()
* @access public
*/
public function add($p_filelist)
{
return $this->addModify($p_filelist, '', '');
}
/**
* @param string $p_path
* @param bool $p_preserve
* @param bool $p_symlinks
* @return bool
*/
public function extract($p_path = '', $p_preserve = false, $p_symlinks = true)
{
return $this->extractModify($p_path, '', $p_preserve, $p_symlinks);
}
/**
* @return array|int
*/
public function listContent()
{
$v_list_detail = array();
if ($this->_openRead()) {
if (!$this->_extractList('', $v_list_detail, "list", '', '')) {
unset($v_list_detail);
$v_list_detail = 0;
}
$this->_close();
}
return $v_list_detail;
}
/**
* This method creates the archive file and add the files / directories
* that are listed in $p_filelist.
* If the file already exists and is writable, it is replaced by the
* new tar. It is a create and not an add. If the file exists and is
* read-only or is a directory it is not replaced. The method return
* false and a PEAR error text.
* The $p_filelist parameter can be an array of string, each string
* representing a filename or a directory name with their path if
* needed. It can also be a single string with names separated by a
* single blank.
* The path indicated in $p_remove_dir will be removed from the
* memorized path of each file / directory listed when this path
* exists. By default nothing is removed (empty path '')
* The path indicated in $p_add_dir will be added at the beginning of
* the memorized path of each file / directory listed. However it can
* be set to empty ''. The adding of a path is done after the removing
* of path.
* The path add/remove ability enables the user to prepare an archive
* for extraction in a different path than the origin files are.
* See also addModify() method for file adding properties.
*
* @param array $p_filelist An array of filenames and directory names,
* or a single string with names separated by
* a single blank space.
* @param string $p_add_dir A string which contains a path to be added
* to the memorized path of each element in
* the list.
* @param string $p_remove_dir A string which contains a path to be
* removed from the memorized path of each
* element in the list, when relevant.
*
* @return boolean true on success, false on error.
* @see addModify()
*/
public function createModify($p_filelist, $p_add_dir, $p_remove_dir = '')
{
$v_result = true;
if (!$this->_openWrite()) {
return false;
}
if ($p_filelist != '') {
if (is_array($p_filelist)) {
$v_list = $p_filelist;
} elseif (is_string($p_filelist)) {
$v_list = explode($this->_separator, $p_filelist);
} else {
$this->_cleanFile();
$this->_error('Invalid file list');
return false;
}
$v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir);
}
if ($v_result) {
$this->_writeFooter();
$this->_close();
} else {
$this->_cleanFile();
}
return $v_result;
}
/**
* This method add the files / directories listed in $p_filelist at the
* end of the existing archive. If the archive does not yet exists it
* is created.
* The $p_filelist parameter can be an array of string, each string
* representing a filename or a directory name with their path if
* needed. It can also be a single string with names separated by a
* single blank.
* The path indicated in $p_remove_dir will be removed from the
* memorized path of each file / directory listed when this path
* exists. By default nothing is removed (empty path '')
* The path indicated in $p_add_dir will be added at the beginning of
* the memorized path of each file / directory listed. However it can
* be set to empty ''. The adding of a path is done after the removing
* of path.
* The path add/remove ability enables the user to prepare an archive
* for extraction in a different path than the origin files are.
* If a file/dir is already in the archive it will only be added at the
* end of the archive. There is no update of the existing archived
* file/dir. However while extracting the archive, the last file will
* replace the first one. This results in a none optimization of the
* archive size.
* If a file/dir does not exist the file/dir is ignored. However an
* error text is send to PEAR error.
* If a file/dir is not readable the file/dir is ignored. However an
* error text is send to PEAR error.
*
* @param array $p_filelist An array of filenames and directory
* names, or a single string with names
* separated by a single blank space.
* @param string $p_add_dir A string which contains a path to be
* added to the memorized path of each
* element in the list.
* @param string $p_remove_dir A string which contains a path to be
* removed from the memorized path of
* each element in the list, when
* relevant.
*
* @return true on success, false on error.
*/
public function addModify($p_filelist, $p_add_dir, $p_remove_dir = '')
{
$v_result = true;
if (!$this->_isArchive()) {
$v_result = $this->createModify(
$p_filelist,
$p_add_dir,
$p_remove_dir
);
} else {
if (is_array($p_filelist)) {
$v_list = $p_filelist;
} elseif (is_string($p_filelist)) {
$v_list = explode($this->_separator, $p_filelist);
} else {
$this->_error('Invalid file list');
return false;
}
$v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
}
return $v_result;
}
/**
* This method add a single string as a file at the
* end of the existing archive. If the archive does not yet exists it
* is created.
*
* @param string $p_filename A string which contains the full
* filename path that will be associated
* with the string.
* @param string $p_string The content of the file added in
* the archive.
* @param bool|int $p_datetime A custom date/time (unix timestamp)
* for the file (optional).
* @param array $p_params An array of optional params:
* stamp => the datetime (replaces
* datetime above if it exists)
* mode => the permissions on the
* file (600 by default)
* type => is this a link? See the
* tar specification for details.
* (default = regular file)
* uid => the user ID of the file
* (default = 0 = root)
* gid => the group ID of the file
* (default = 0 = root)
*
* @return true on success, false on error.
*/
public function addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
{
$p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
$p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
$p_type = @$p_params["type"] ? $p_params["type"] : "";
$p_uid = @$p_params["uid"] ? $p_params["uid"] : "";
$p_gid = @$p_params["gid"] ? $p_params["gid"] : "";
$v_result = true;
if (!$this->_isArchive()) {
if (!$this->_openWrite()) {
return false;
}
$this->_close();
}
if (!$this->_openAppend()) {
return false;
}
// Need to check the get back to the temporary file ? ....
$v_result = $this->_addString($p_filename, $p_string, $p_datetime, $p_params);
$this->_writeFooter();
$this->_close();
return $v_result;
}
/**
* This method extract all the content of the archive in the directory
* indicated by $p_path. When relevant the memorized path of the
* files/dir can be modified by removing the $p_remove_path path at the
* beginning of the file/dir path.
* While extracting a file, if the directory path does not exists it is
* created.
* While extracting a file, if the file already exists it is replaced
* without looking for last modification date.
* While extracting a file, if the file already exists and is write
* protected, the extraction is aborted.
* While extracting a file, if a directory with the same name already
* exists, the extraction is aborted.
* While extracting a directory, if a file with the same name already
* exists, the extraction is aborted.
* While extracting a file/directory if the destination directory exist
* and is write protected, or does not exist but can not be created,
* the extraction is aborted.
* If after extraction an extracted file does not show the correct
* stored file size, the extraction is aborted.
* When the extraction is aborted, a PEAR error text is set and false
* is returned. However the result can be a partial extraction that may
* need to be manually cleaned.
*
* @param string $p_path The path of the directory where the
* files/dir need to by extracted.
* @param string $p_remove_path Part of the memorized path that can be
* removed if present at the beginning of
* the file/dir path.
* @param boolean $p_preserve Preserve user/group ownership of files
* @param boolean $p_symlinks Allow symlinks.
*
* @return boolean true on success, false on error.
* @see extractList()
*/
public function extractModify($p_path, $p_remove_path, $p_preserve = false, $p_symlinks = true)
{
$v_result = true;
$v_list_detail = array();
if ($v_result = $this->_openRead()) {
$v_result = $this->_extractList(
$p_path,
$v_list_detail,
"complete",
0,
$p_remove_path,
$p_preserve,
$p_symlinks
);
$this->_close();
}
return $v_result;
}
/**
* This method extract from the archive one file identified by $p_filename.
* The return value is a string with the file content, or NULL on error.
*
* @param string $p_filename The path of the file to extract in a string.
*
* @return a string with the file content or NULL.
*/
public function extractInString($p_filename)
{
if ($this->_openRead()) {
$v_result = $this->_extractInString($p_filename);
$this->_close();
} else {
$v_result = null;
}
return $v_result;
}
/**
* This method extract from the archive only the files indicated in the
* $p_filelist. These files are extracted in the current directory or
* in the directory indicated by the optional $p_path parameter.
* If indicated the $p_remove_path can be used in the same way as it is
* used in extractModify() method.
*
* @param array $p_filelist An array of filenames and directory names,
* or a single string with names separated
* by a single blank space.
* @param string $p_path The path of the directory where the
* files/dir need to by extracted.
* @param string $p_remove_path Part of the memorized path that can be
* removed if present at the beginning of
* the file/dir path.
* @param boolean $p_preserve Preserve user/group ownership of files
* @param boolean $p_symlinks Allow symlinks.
*
* @return true on success, false on error.
* @see extractModify()
*/
public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false, $p_symlinks = true)
{
$v_result = true;
$v_list_detail = array();
if (is_array($p_filelist)) {
$v_list = $p_filelist;
} elseif (is_string($p_filelist)) {
$v_list = explode($this->_separator, $p_filelist);
} else {
$this->_error('Invalid string list');
return false;
}
if ($v_result = $this->_openRead()) {
$v_result = $this->_extractList(
$p_path,
$v_list_detail,
"partial",
$v_list,
$p_remove_path,
$p_preserve,
$p_symlinks
);
$this->_close();
}
return $v_result;
}
/**
* This method set specific attributes of the archive. It uses a variable
* list of parameters, in the format attribute code + attribute values :
* $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ',');
*
* @return true on success, false on error.
*/
public function setAttribute()
{
$v_result = true;
// ----- Get the number of variable list of arguments
if (($v_size = func_num_args()) == 0) {
return true;
}
// ----- Get the arguments
$v_att_list = func_get_args();
// ----- Read the attributes
$i = 0;
while ($i < $v_size) {
// ----- Look for next option
switch ($v_att_list[$i]) {
// ----- Look for options that request a string value
case ARCHIVE_TAR_ATT_SEPARATOR :
// ----- Check the number of parameters
if (($i + 1) >= $v_size) {
$this->_error(
'Invalid number of parameters for '
. 'attribute ARCHIVE_TAR_ATT_SEPARATOR'
);
return false;
}
// ----- Get the value
$this->_separator = $v_att_list[$i + 1];
$i++;
break;
default :
$this->_error('Unknown attribute code ' . $v_att_list[$i] . '');
return false;
}
// ----- Next attribute
$i++;
}
return $v_result;
}
/**
* This method sets the regular expression for ignoring files and directories
* at import, for example:
* $arch->setIgnoreRegexp("#CVS|\.svn#");
*
* @param string $regexp regular expression defining which files or directories to ignore
*/
public function setIgnoreRegexp($regexp)
{
$this->_ignore_regexp = $regexp;
}
/**
* This method sets the regular expression for ignoring all files and directories
* matching the filenames in the array list at import, for example:
* $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool'));
*
* @param array $list a list of file or directory names to ignore
*
* @access public
*/
public function setIgnoreList($list)
{
$regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list);
$regexp = '#/' . join('$|/', $list) . '#';
$this->setIgnoreRegexp($regexp);
}
/**
* @param string $p_message
*/
public function _error($p_message)
{
$this->error_object = $this->raiseError($p_message);
}
/**
* @param string $p_message
*/
public function _warning($p_message)
{
$this->error_object = $this->raiseError($p_message);
}
/**
* @param string $p_filename
* @return bool
*/
public function _isArchive($p_filename = null)
{
if ($p_filename == null) {
$p_filename = $this->_tarname;
}
clearstatcache();
return @is_file($p_filename) && !@is_link($p_filename);
}
/**
* @return bool
*/
public function _openWrite()
{
if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
$this->_file = @gzopen($this->_tarname, "wb9");
} else {
if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
$this->_file = @bzopen($this->_tarname, "w");
} else {
if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
$this->_file = @xzopen($this->_tarname, 'w');
} else {
if ($this->_compress_type == 'none') {
$this->_file = @fopen($this->_tarname, "wb");
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
return false;
}
}
}
}
if ($this->_file == 0) {
$this->_error(
'Unable to open in write mode \''
. $this->_tarname . '\''
);
return false;
}
return true;
}
/**
* @return bool
*/
public function _openRead()
{
if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') {
// ----- Look if a local copy need to be done
if ($this->_temp_tarname == '') {
$this->_temp_tarname = uniqid('tar') . '.tmp';
if (!$v_file_from = @fopen($this->_tarname, 'rb')) {
$this->_error(
'Unable to open in read mode \''
. $this->_tarname . '\''
);
$this->_temp_tarname = '';
return false;
}
if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) {
$this->_error(
'Unable to open in write mode \''
. $this->_temp_tarname . '\''
);
$this->_temp_tarname = '';
return false;
}
while ($v_data = @fread($v_file_from, 1024)) {
@fwrite($v_file_to, $v_data);
}
@fclose($v_file_from);
@fclose($v_file_to);
}
// ----- File to open if the local copy
$v_filename = $this->_temp_tarname;
} else {
// ----- File to open if the normal Tar file
$v_filename = $this->_tarname;
}
if ($this->_compress_type == 'gz' && function_exists('gzopen')) {
$this->_file = @gzopen($v_filename, "rb");
} else {
if ($this->_compress_type == 'bz2' && function_exists('bzopen')) {
$this->_file = @bzopen($v_filename, "r");
} else {
if ($this->_compress_type == 'lzma2' && function_exists('xzopen')) {
$this->_file = @xzopen($v_filename, "r");
} else {
if ($this->_compress_type == 'none') {
$this->_file = @fopen($v_filename, "rb");
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
return false;
}
}
}
}
if ($this->_file == 0) {
$this->_error('Unable to open in read mode \'' . $v_filename . '\'');
return false;
}
return true;
}
/**
* @return bool
*/
public function _openReadWrite()
{
if ($this->_compress_type == 'gz') {
$this->_file = @gzopen($this->_tarname, "r+b");
} else {
if ($this->_compress_type == 'bz2') {
$this->_error(
'Unable to open bz2 in read/write mode \''
. $this->_tarname . '\' (limitation of bz2 extension)'
);
return false;
} else {
if ($this->_compress_type == 'lzma2') {
$this->_error(
'Unable to open lzma2 in read/write mode \''
. $this->_tarname . '\' (limitation of lzma2 extension)'
);
return false;
} else {
if ($this->_compress_type == 'none') {
$this->_file = @fopen($this->_tarname, "r+b");
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
return false;
}
}
}
}
if ($this->_file == 0) {
$this->_error(
'Unable to open in read/write mode \''
. $this->_tarname . '\''
);
return false;
}
return true;
}
/**
* @return bool
*/
public function _close()
{
//if (isset($this->_file)) {
if (is_resource($this->_file)) {
if ($this->_compress_type == 'gz') {
@gzclose($this->_file);
} else {
if ($this->_compress_type == 'bz2') {
@bzclose($this->_file);
} else {
if ($this->_compress_type == 'lzma2') {
@xzclose($this->_file);
} else {
if ($this->_compress_type == 'none') {
@fclose($this->_file);
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
}
}
}
}
$this->_file = 0;
}
// ----- Look if a local copy need to be erase
// Note that it might be interesting to keep the url for a time : ToDo
if ($this->_temp_tarname != '') {
@unlink($this->_temp_tarname);
$this->_temp_tarname = '';
}
return true;
}
/**
* @return bool
*/
public function _cleanFile()
{
$this->_close();
// ----- Look for a local copy
if ($this->_temp_tarname != '') {
// ----- Remove the local copy but not the remote tarname
@unlink($this->_temp_tarname);
$this->_temp_tarname = '';
} else {
// ----- Remove the local tarname file
@unlink($this->_tarname);
}
$this->_tarname = '';
return true;
}
/**
* @param mixed $p_binary_data
* @param integer $p_len
* @return bool
*/
public function _writeBlock($p_binary_data, $p_len = null)
{
if (is_resource($this->_file)) {
if ($p_len === null) {
if ($this->_compress_type == 'gz') {
@gzputs($this->_file, $p_binary_data);
} else {
if ($this->_compress_type == 'bz2') {
@bzwrite($this->_file, $p_binary_data);
} else {
if ($this->_compress_type == 'lzma2') {
@xzwrite($this->_file, $p_binary_data);
} else {
if ($this->_compress_type == 'none') {
@fputs($this->_file, $p_binary_data);
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
}
}
}
}
} else {
if ($this->_compress_type == 'gz') {
@gzputs($this->_file, $p_binary_data, $p_len);
} else {
if ($this->_compress_type == 'bz2') {
@bzwrite($this->_file, $p_binary_data, $p_len);
} else {
if ($this->_compress_type == 'lzma2') {
@xzwrite($this->_file, $p_binary_data, $p_len);
} else {
if ($this->_compress_type == 'none') {
@fputs($this->_file, $p_binary_data, $p_len);
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
}
}
}
}
}
}
return true;
}
/**
* @return null|string
*/
public function _readBlock()
{
$v_block = null;
if (is_resource($this->_file)) {
if ($this->_compress_type == 'gz') {
$v_block = @gzread($this->_file, 512);
} else {
if ($this->_compress_type == 'bz2') {
$v_block = @bzread($this->_file, 512);
} else {
if ($this->_compress_type == 'lzma2') {
$v_block = @xzread($this->_file, 512);
} else {
if ($this->_compress_type == 'none') {
$v_block = @fread($this->_file, 512);
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
}
}
}
}
}
return $v_block;
}
/**
* @param null $p_len
* @return bool
*/
public function _jumpBlock($p_len = null)
{
if (is_resource($this->_file)) {
if ($p_len === null) {
$p_len = 1;
}
if ($this->_compress_type == 'gz') {
@gzseek($this->_file, gztell($this->_file) + ($p_len * 512));
} else {
if ($this->_compress_type == 'bz2') {
// ----- Replace missing bztell() and bzseek()
for ($i = 0; $i < $p_len; $i++) {
$this->_readBlock();
}
} else {
if ($this->_compress_type == 'lzma2') {
// ----- Replace missing xztell() and xzseek()
for ($i = 0; $i < $p_len; $i++) {
$this->_readBlock();
}
} else {
if ($this->_compress_type == 'none') {
@fseek($this->_file, $p_len * 512, SEEK_CUR);
} else {
$this->_error(
'Unknown or missing compression type ('
. $this->_compress_type . ')'
);
}
}
}
}
}
return true;
}
/**
* @return bool
*/
public function _writeFooter()
{
if (is_resource($this->_file)) {
// ----- Write the last 0 filled block for end of archive
$v_binary_data = pack('a1024', '');
$this->_writeBlock($v_binary_data);
}
return true;
}
/**
* @param array $p_list
* @param string $p_add_dir
* @param string $p_remove_dir
* @return bool
*/
public function _addList($p_list, $p_add_dir, $p_remove_dir)
{
$v_result = true;
$v_header = array();
// ----- Remove potential windows directory separator
$p_add_dir = $this->_translateWinPath($p_add_dir);
$p_remove_dir = $this->_translateWinPath($p_remove_dir, false);
if (!$this->_file) {
$this->_error('Invalid file descriptor');
return false;
}
if (sizeof($p_list) == 0) {
return true;
}
foreach ($p_list as $v_filename) {
if (!$v_result) {
break;
}
// ----- Skip the current tar name
if ($v_filename == $this->_tarname) {
continue;
}
if ($v_filename == '') {
continue;
}
// ----- ignore files and directories matching the ignore regular expression
if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/' . $v_filename)) {
$this->_warning("File '$v_filename' ignored");
continue;
}
if (!file_exists($v_filename) && !is_link($v_filename)) {
$this->_warning("File '$v_filename' does not exist");
continue;
}
// ----- Add the file or directory header
if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) {
return false;
}
if (@is_dir($v_filename) && !@is_link($v_filename)) {
if (!($p_hdir = opendir($v_filename))) {
$this->_warning("Directory '$v_filename' can not be read");
continue;
}
while (false !== ($p_hitem = readdir($p_hdir))) {
if (($p_hitem != '.') && ($p_hitem != '..')) {
if ($v_filename != ".") {
$p_temp_list[0] = $v_filename . '/' . $p_hitem;
} else {
$p_temp_list[0] = $p_hitem;
}
$v_result = $this->_addList(
$p_temp_list,
$p_add_dir,
$p_remove_dir
);
}
}
unset($p_temp_list);
unset($p_hdir);
unset($p_hitem);
}
}
return $v_result;
}
/**
* @param string $p_filename
* @param mixed $p_header
* @param string $p_add_dir
* @param string $p_remove_dir
* @param null $v_stored_filename
* @return bool
*/
public function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir, $v_stored_filename = null)
{
if (!$this->_file) {
$this->_error('Invalid file descriptor');
return false;
}
if ($p_filename == '') {
$this->_error('Invalid file name');
return false;
}
if (is_null($v_stored_filename)) {
// ----- Calculate the stored filename
$p_filename = $this->_translateWinPath($p_filename, false);
$v_stored_filename = $p_filename;
if (strcmp($p_filename, $p_remove_dir) == 0) {
return true;
}
if ($p_remove_dir != '') {
if (substr($p_remove_dir, -1) != '/') {
$p_remove_dir .= '/';
}
if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) {
$v_stored_filename = substr($p_filename, strlen($p_remove_dir));
}
}
$v_stored_filename = $this->_translateWinPath($v_stored_filename);
if ($p_add_dir != '') {
if (substr($p_add_dir, -1) == '/') {
$v_stored_filename = $p_add_dir . $v_stored_filename;
} else {
$v_stored_filename = $p_add_dir . '/' . $v_stored_filename;
}
}
$v_stored_filename = $this->_pathReduction($v_stored_filename);
}
if ($this->_isArchive($p_filename)) {
if (($v_file = @fopen($p_filename, "rb")) == 0) {
$this->_warning(
"Unable to open file '" . $p_filename
. "' in binary read mode"
);
return true;
}
if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
return false;
}
while (($v_buffer = fread($v_file, $this->buffer_length)) != '') {
$buffer_length = strlen("$v_buffer");
if ($buffer_length != $this->buffer_length) {
$pack_size = ((int)($buffer_length / 512) + 1) * 512;
$pack_format = sprintf('a%d', $pack_size);
} else {
$pack_format = sprintf('a%d', $this->buffer_length);
}
$v_binary_data = pack($pack_format, "$v_buffer");
$this->_writeBlock($v_binary_data);
}
fclose($v_file);
} else {
// ----- Only header for dir
if (!$this->_writeHeader($p_filename, $v_stored_filename)) {
return false;
}
}
return true;
}
/**
* @param string $p_filename
* @param string $p_string
* @param bool $p_datetime
* @param array $p_params
* @return bool
*/
public function _addString($p_filename, $p_string, $p_datetime = false, $p_params = array())
{
$p_stamp = @$p_params["stamp"] ? $p_params["stamp"] : ($p_datetime ? $p_datetime : time());
$p_mode = @$p_params["mode"] ? $p_params["mode"] : 0600;
$p_type = @$p_params["type"] ? $p_params["type"] : "";
$p_uid = @$p_params["uid"] ? $p_params["uid"] : 0;
$p_gid = @$p_params["gid"] ? $p_params["gid"] : 0;
if (!$this->_file) {
$this->_error('Invalid file descriptor');
return false;
}
if ($p_filename == '') {
$this->_error('Invalid file name');
return false;
}
// ----- Calculate the stored filename
$p_filename = $this->_translateWinPath($p_filename, false);
// ----- If datetime is not specified, set current time
if ($p_datetime === false) {
$p_datetime = time();
}
if (!$this->_writeHeaderBlock(
$p_filename,
strlen($p_string),
$p_stamp,
$p_mode,
$p_type,
$p_uid,
$p_gid
)
) {
return false;
}
$i = 0;
while (($v_buffer = substr($p_string, (($i++) * 512), 512)) != '') {
$v_binary_data = pack("a512", $v_buffer);
$this->_writeBlock($v_binary_data);
}
return true;
}
/**
* @param string $p_filename
* @param string $p_stored_filename
* @return bool
*/
public function _writeHeader($p_filename, $p_stored_filename)
{
if ($p_stored_filename == '') {
$p_stored_filename = $p_filename;
}
$v_reduced_filename = $this->_pathReduction($p_stored_filename);
if (strlen($v_reduced_filename) > 99) {
if (!$this->_writeLongHeader($v_reduced_filename, false)) {
return false;
}
}
$v_linkname = '';
if (@is_link($p_filename)) {
$v_linkname = readlink($p_filename);
}
if (strlen($v_linkname) > 99) {
if (!$this->_writeLongHeader($v_linkname, true)) {
return false;
}
}
$v_info = lstat($p_filename);
$v_uid = sprintf("%07s", DecOct($v_info[4]));
$v_gid = sprintf("%07s", DecOct($v_info[5]));
$v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777));
$v_mtime = sprintf("%011s", DecOct($v_info['mtime']));
if (@is_link($p_filename)) {
$v_typeflag = '2';
$v_size = sprintf("%011s", DecOct(0));
} elseif (@is_dir($p_filename)) {
$v_typeflag = "5";
$v_size = sprintf("%011s", DecOct(0));
} else {
$v_typeflag = '0';
clearstatcache();
$v_size = sprintf("%011s", DecOct($v_info['size']));
}
$v_magic = 'ustar ';
$v_version = ' ';
if (function_exists('posix_getpwuid')) {
$userinfo = posix_getpwuid($v_info[4]);
$groupinfo = posix_getgrgid($v_info[5]);
$v_uname = $userinfo['name'];
$v_gname = $groupinfo['name'];
} else {
$v_uname = '';
$v_gname = '';
}
$v_devmajor = '';
$v_devminor = '';
$v_prefix = '';
$v_binary_data_first = pack(
"a100a8a8a8a12a12",
$v_reduced_filename,
$v_perms,
$v_uid,
$v_gid,
$v_size,
$v_mtime
);
$v_binary_data_last = pack(
"a1a100a6a2a32a32a8a8a155a12",
$v_typeflag,
$v_linkname,
$v_magic,
$v_version,
$v_uname,
$v_gname,
$v_devmajor,
$v_devminor,
$v_prefix,
''
);
// ----- Calculate the checksum
$v_checksum = 0;
// ..... First part of the header
for ($i = 0; $i < 148; $i++) {
$v_checksum += ord(substr($v_binary_data_first, $i, 1));
}
// ..... Ignore the checksum value and replace it by ' ' (space)
for ($i = 148; $i < 156; $i++) {
$v_checksum += ord(' ');
}
// ..... Last part of the header
for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
$v_checksum += ord(substr($v_binary_data_last, $j, 1));
}
// ----- Write the first 148 bytes of the header in the archive
$this->_writeBlock($v_binary_data_first, 148);
// ----- Write the calculated checksum
$v_checksum = sprintf("%06s\0 ", DecOct($v_checksum));
$v_binary_data = pack("a8", $v_checksum);
$this->_writeBlock($v_binary_data, 8);
// ----- Write the last 356 bytes of the header in the archive
$this->_writeBlock($v_binary_data_last, 356);
return true;
}
/**
* @param string $p_filename
* @param int $p_size
* @param int $p_mtime
* @param int $p_perms
* @param string $p_type
* @param int $p_uid
* @param int $p_gid
* @return bool
*/
public function _writeHeaderBlock(
$p_filename,
$p_size,
$p_mtime = 0,
$p_perms = 0,
$p_type = '',
$p_uid = 0,
$p_gid = 0
)
{
$p_filename = $this->_pathReduction($p_filename);
if (strlen($p_filename) > 99) {
if (!$this->_writeLongHeader($p_filename, false)) {
return false;
}
}
if ($p_type == "5") {
$v_size = sprintf("%011s", DecOct(0));
} else {
$v_size = sprintf("%011s", DecOct($p_size));
}
$v_uid = sprintf("%07s", DecOct($p_uid));
$v_gid = sprintf("%07s", DecOct($p_gid));
$v_perms = sprintf("%07s", DecOct($p_perms & 000777));
$v_mtime = sprintf("%11s", DecOct($p_mtime));
$v_linkname = '';
$v_magic = 'ustar ';
$v_version = ' ';
if (function_exists('posix_getpwuid')) {
$userinfo = posix_getpwuid($p_uid);
$groupinfo = posix_getgrgid($p_gid);
$v_uname = $userinfo['name'];
$v_gname = $groupinfo['name'];
} else {
$v_uname = '';
$v_gname = '';
}
$v_devmajor = '';
$v_devminor = '';
$v_prefix = '';
$v_binary_data_first = pack(
"a100a8a8a8a12A12",
$p_filename,
$v_perms,
$v_uid,
$v_gid,
$v_size,
$v_mtime
);
$v_binary_data_last = pack(
"a1a100a6a2a32a32a8a8a155a12",
$p_type,
$v_linkname,
$v_magic,
$v_version,
$v_uname,
$v_gname,
$v_devmajor,
$v_devminor,
$v_prefix,
''
);
// ----- Calculate the checksum
$v_checksum = 0;
// ..... First part of the header
for ($i = 0; $i < 148; $i++) {
$v_checksum += ord(substr($v_binary_data_first, $i, 1));
}
// ..... Ignore the checksum value and replace it by ' ' (space)
for ($i = 148; $i < 156; $i++) {
$v_checksum += ord(' ');
}
// ..... Last part of the header
for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
$v_checksum += ord(substr($v_binary_data_last, $j, 1));
}
// ----- Write the first 148 bytes of the header in the archive
$this->_writeBlock($v_binary_data_first, 148);
// ----- Write the calculated checksum
$v_checksum = sprintf("%06s ", DecOct($v_checksum));
$v_binary_data = pack("a8", $v_checksum);
$this->_writeBlock($v_binary_data, 8);
// ----- Write the last 356 bytes of the header in the archive
$this->_writeBlock($v_binary_data_last, 356);
return true;
}
/**
* @param string $p_filename
* @return bool
*/
public function _writeLongHeader($p_filename, $is_link = false)
{
$v_uid = sprintf("%07s", 0);
$v_gid = sprintf("%07s", 0);
$v_perms = sprintf("%07s", 0);
$v_size = sprintf("%'011s", DecOct(strlen($p_filename)));
$v_mtime = sprintf("%011s", 0);
$v_typeflag = ($is_link ? 'K' : 'L');
$v_linkname = '';
$v_magic = 'ustar ';
$v_version = ' ';
$v_uname = '';
$v_gname = '';
$v_devmajor = '';
$v_devminor = '';
$v_prefix = '';
$v_binary_data_first = pack(
"a100a8a8a8a12a12",
'././@LongLink',
$v_perms,
$v_uid,
$v_gid,
$v_size,
$v_mtime
);
$v_binary_data_last = pack(
"a1a100a6a2a32a32a8a8a155a12",
$v_typeflag,
$v_linkname,
$v_magic,
$v_version,
$v_uname,
$v_gname,
$v_devmajor,
$v_devminor,
$v_prefix,
''
);
// ----- Calculate the checksum
$v_checksum = 0;
// ..... First part of the header
for ($i = 0; $i < 148; $i++) {
$v_checksum += ord(substr($v_binary_data_first, $i, 1));
}
// ..... Ignore the checksum value and replace it by ' ' (space)
for ($i = 148; $i < 156; $i++) {
$v_checksum += ord(' ');
}
// ..... Last part of the header
for ($i = 156, $j = 0; $i < 512; $i++, $j++) {
$v_checksum += ord(substr($v_binary_data_last, $j, 1));
}
// ----- Write the first 148 bytes of the header in the archive
$this->_writeBlock($v_binary_data_first, 148);
// ----- Write the calculated checksum
$v_checksum = sprintf("%06s\0 ", DecOct($v_checksum));
$v_binary_data = pack("a8", $v_checksum);
$this->_writeBlock($v_binary_data, 8);
// ----- Write the last 356 bytes of the header in the archive
$this->_writeBlock($v_binary_data_last, 356);
// ----- Write the filename as content of the block
$i = 0;
while (($v_buffer = substr($p_filename, (($i++) * 512), 512)) != '') {
$v_binary_data = pack("a512", "$v_buffer");
$this->_writeBlock($v_binary_data);
}
return true;
}
/**
* @param mixed $v_binary_data
* @param mixed $v_header
* @return bool
*/
public function _readHeader($v_binary_data, &$v_header)
{
if (strlen($v_binary_data) == 0) {
$v_header['filename'] = '';
return true;
}
if (strlen($v_binary_data) != 512) {
$v_header['filename'] = '';
$this->_error('Invalid block size : ' . strlen($v_binary_data));
return false;
}
if (!is_array($v_header)) {
$v_header = array();
}
// ----- Calculate the checksum
$v_checksum = 0;
// ..... First part of the header
$v_binary_split = str_split($v_binary_data);
$v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 0, 148)));
$v_checksum += array_sum(array_map('ord', array(' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',)));
$v_checksum += array_sum(array_map('ord', array_slice($v_binary_split, 156, 512)));
$v_data = unpack($this->_fmt, $v_binary_data);
if (strlen($v_data["prefix"]) > 0) {
$v_data["filename"] = "$v_data[prefix]/$v_data[filename]";
}
// ----- Extract the checksum
$v_data_checksum = trim($v_data['checksum']);
if (!preg_match('/^[0-7]*$/', $v_data_checksum)) {
$this->_error(
'Invalid checksum for file "' . $v_data['filename']
. '" : ' . $v_data_checksum . ' extracted'
);
return false;
}
$v_header['checksum'] = OctDec($v_data_checksum);
if ($v_header['checksum'] != $v_checksum) {
$v_header['filename'] = '';
// ----- Look for last block (empty block)
if (($v_checksum == 256) && ($v_header['checksum'] == 0)) {
return true;
}
$this->_error(
'Invalid checksum for file "' . $v_data['filename']
. '" : ' . $v_checksum . ' calculated, '
. $v_header['checksum'] . ' expected'
);
return false;
}
// ----- Extract the properties
$v_header['filename'] = rtrim($v_data['filename'], "\0");
if ($this->_maliciousFilename($v_header['filename'])) {
$this->_error(
'Malicious .tar detected, file "' . $v_header['filename'] .
'" will not install in desired directory tree'
);
return false;
}
$v_header['mode'] = OctDec(trim($v_data['mode']));
$v_header['uid'] = OctDec(trim($v_data['uid']));
$v_header['gid'] = OctDec(trim($v_data['gid']));
$v_header['size'] = $this->_tarRecToSize($v_data['size']);
$v_header['mtime'] = OctDec(trim($v_data['mtime']));
if (($v_header['typeflag'] = $v_data['typeflag']) == "5") {
$v_header['size'] = 0;
}
$v_header['link'] = trim($v_data['link']);
/* ----- All these fields are removed form the header because
they do not carry interesting info
$v_header[magic] = trim($v_data[magic]);
$v_header[version] = trim($v_data[version]);
$v_header[uname] = trim($v_data[uname]);
$v_header[gname] = trim($v_data[gname]);
$v_header[devmajor] = trim($v_data[devmajor]);
$v_header[devminor] = trim($v_data[devminor]);
*/
return true;
}
/**
* Convert Tar record size to actual size
*
* @param string $tar_size
* @return size of tar record in bytes
*/
private function _tarRecToSize($tar_size)
{
/*
* First byte of size has a special meaning if bit 7 is set.
*
* Bit 7 indicates base-256 encoding if set.
* Bit 6 is the sign bit.
* Bits 5:0 are most significant value bits.
*/
$ch = ord($tar_size[0]);
if ($ch & 0x80) {
// Full 12-bytes record is required.
$rec_str = $tar_size . "\x00";
$size = ($ch & 0x40) ? -1 : 0;
$size = ($size << 6) | ($ch & 0x3f);
for ($num_ch = 1; $num_ch < 12; ++$num_ch) {
$size = ($size * 256) + ord($rec_str[$num_ch]);
}
return $size;
} else {
return OctDec(trim($tar_size));
}
}
/**
* Detect and report a malicious file name
*
* @param string $file
*
* @return bool
*/
private function _maliciousFilename($file)
{
if (strpos($file, 'phar://') === 0) {
return true;
}
if (strpos($file, '../') !== false || strpos($file, '..\\') !== false) {
return true;
}
return false;
}
/**
* @param $v_header
* @return bool
*/
public function _readLongHeader(&$v_header)
{
$v_filename = '';
$v_filesize = $v_header['size'];
$n = floor($v_header['size'] / 512);
for ($i = 0; $i < $n; $i++) {
$v_content = $this->_readBlock();
$v_filename .= $v_content;
}
if (($v_header['size'] % 512) != 0) {
$v_content = $this->_readBlock();
$v_filename .= $v_content;
}
// ----- Read the next header
$v_binary_data = $this->_readBlock();
if (!$this->_readHeader($v_binary_data, $v_header)) {
return false;
}
$v_filename = rtrim(substr($v_filename, 0, $v_filesize), "\0");
$v_header['filename'] = $v_filename;
if ($this->_maliciousFilename($v_filename)) {
$this->_error(
'Malicious .tar detected, file "' . $v_filename .
'" will not install in desired directory tree'
);
return false;
}
return true;
}
/**
* This method extract from the archive one file identified by $p_filename.
* The return value is a string with the file content, or null on error.
*
* @param string $p_filename The path of the file to extract in a string.
*
* @return a string with the file content or null.
*/
private function _extractInString($p_filename)
{
$v_result_str = "";
while (strlen($v_binary_data = $this->_readBlock()) != 0) {
if (!$this->_readHeader($v_binary_data, $v_header)) {
return null;
}
if ($v_header['filename'] == '') {
continue;
}
switch ($v_header['typeflag']) {
case 'L':
{
if (!$this->_readLongHeader($v_header)) {
return null;
}
}
break;
case 'K':
{
$v_link_header = $v_header;
if (!$this->_readLongHeader($v_link_header)) {
return null;
}
$v_header['link'] = $v_link_header['filename'];
}
break;
}
if ($v_header['filename'] == $p_filename) {
if ($v_header['typeflag'] == "5") {
$this->_error(
'Unable to extract in string a directory '
. 'entry {' . $v_header['filename'] . '}'
);
return null;
} else {
$n = floor($v_header['size'] / 512);
for ($i = 0; $i < $n; $i++) {
$v_result_str .= $this->_readBlock();
}
if (($v_header['size'] % 512) != 0) {
$v_content = $this->_readBlock();
$v_result_str .= substr(
$v_content,
0,
($v_header['size'] % 512)
);
}
return $v_result_str;
}
} else {
$this->_jumpBlock(ceil(($v_header['size'] / 512)));
}
}
return null;
}
/**
* @param string $p_path
* @param string $p_list_detail
* @param string $p_mode
* @param string $p_file_list
* @param string $p_remove_path
* @param bool $p_preserve
* @param bool $p_symlinks
* @return bool
*/
public function _extractList(
$p_path,
&$p_list_detail,
$p_mode,
$p_file_list,
$p_remove_path,
$p_preserve = false,
$p_symlinks = true
)
{
$v_result = true;
$v_nb = 0;
$v_extract_all = true;
$v_listing = false;
$p_path = $this->_translateWinPath($p_path, false);
if ($p_path == '' || (substr($p_path, 0, 1) != '/'
&& substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))
) {
$p_path = "./" . $p_path;
}
$p_remove_path = $this->_translateWinPath($p_remove_path);
// ----- Look for path to remove format (should end by /)
if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) {
$p_remove_path .= '/';
}
$p_remove_path_size = strlen($p_remove_path);
switch ($p_mode) {
case "complete" :
$v_extract_all = true;
$v_listing = false;
break;
case "partial" :
$v_extract_all = false;
$v_listing = false;
break;
case "list" :
$v_extract_all = false;
$v_listing = true;
break;
default :
$this->_error('Invalid extract mode (' . $p_mode . ')');
return false;
}
clearstatcache();
while (strlen($v_binary_data = $this->_readBlock()) != 0) {
$v_extract_file = false;
$v_extraction_stopped = 0;
if (!$this->_readHeader($v_binary_data, $v_header)) {
return false;
}
if ($v_header['filename'] == '') {
continue;
}
switch ($v_header['typeflag']) {
case 'L':
{
if (!$this->_readLongHeader($v_header)) {
return null;
}
}
break;
case 'K':
{
$v_link_header = $v_header;
if (!$this->_readLongHeader($v_link_header)) {
return null;
}
$v_header['link'] = $v_link_header['filename'];
}
break;
}
// ignore extended / pax headers
if ($v_header['typeflag'] == 'x' || $v_header['typeflag'] == 'g') {
$this->_jumpBlock(ceil(($v_header['size'] / 512)));
continue;
}
if ((!$v_extract_all) && (is_array($p_file_list))) {
// ----- By default no unzip if the file is not found
$v_extract_file = false;
for ($i = 0; $i < sizeof($p_file_list); $i++) {
// ----- Look if it is a directory
if (substr($p_file_list[$i], -1) == '/') {
// ----- Look if the directory is in the filename path
if ((strlen($v_header['filename']) > strlen($p_file_list[$i]))
&& (substr($v_header['filename'], 0, strlen($p_file_list[$i]))
== $p_file_list[$i])
) {
$v_extract_file = true;
break;
}
} // ----- It is a file, so compare the file names
elseif ($p_file_list[$i] == $v_header['filename']) {
$v_extract_file = true;
break;
}
}
} else {
$v_extract_file = true;
}
// ----- Look if this file need to be extracted
if (($v_extract_file) && (!$v_listing)) {
if (($p_remove_path != '')
&& (substr($v_header['filename'] . '/', 0, $p_remove_path_size)
== $p_remove_path)
) {
$v_header['filename'] = substr(
$v_header['filename'],
$p_remove_path_size
);
if ($v_header['filename'] == '') {
continue;
}
}
if (($p_path != './') && ($p_path != '/')) {
while (substr($p_path, -1) == '/') {
$p_path = substr($p_path, 0, strlen($p_path) - 1);
}
if (substr($v_header['filename'], 0, 1) == '/') {
$v_header['filename'] = $p_path . $v_header['filename'];
} else {
$v_header['filename'] = $p_path . '/' . $v_header['filename'];
}
}
if (file_exists($v_header['filename'])) {
if ((@is_dir($v_header['filename']))
&& ($v_header['typeflag'] == '')
) {
$this->_error(
'File ' . $v_header['filename']
. ' already exists as a directory'
);
return false;
}
if (($this->_isArchive($v_header['filename']))
&& ($v_header['typeflag'] == "5")
) {
$this->_error(
'Directory ' . $v_header['filename']
. ' already exists as a file'
);
return false;
}
if (!is_writeable($v_header['filename'])) {
$this->_error(
'File ' . $v_header['filename']
. ' already exists and is write protected'
);
return false;
}
if (filemtime($v_header['filename']) > $v_header['mtime']) {
// To be completed : An error or silent no replace ?
}
} // ----- Check the directory availability and create it if necessary
elseif (($v_result
= $this->_dirCheck(
($v_header['typeflag'] == "5"
? $v_header['filename']
: dirname($v_header['filename']))
)) != 1
) {
$this->_error('Unable to create path for ' . $v_header['filename']);
return false;
}
if ($v_extract_file) {
if ($v_header['typeflag'] == "5") {
if (!@file_exists($v_header['filename'])) {
if (!@mkdir($v_header['filename'], 0777)) {
$this->_error(
'Unable to create directory {'
. $v_header['filename'] . '}'
);
return false;
}
}
} elseif ($v_header['typeflag'] == "2") {
if (!$p_symlinks) {
$this->_warning('Symbolic links are not allowed. '
. 'Unable to extract {'
. $v_header['filename'] . '}'
);
return false;
}
if (@file_exists($v_header['filename'])) {
@unlink($v_header['filename']);
}
if (!@symlink($v_header['link'], $v_header['filename'])) {
$this->_error(
'Unable to extract symbolic link {'
. $v_header['filename'] . '}'
);
return false;
}
} else {
if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) {
$this->_error(
'Error while opening {' . $v_header['filename']
. '} in write binary mode'
);
return false;
} else {
$n = floor($v_header['size'] / 512);
for ($i = 0; $i < $n; $i++) {
$v_content = $this->_readBlock();
fwrite($v_dest_file, $v_content, 512);
}
if (($v_header['size'] % 512) != 0) {
$v_content = $this->_readBlock();
fwrite($v_dest_file, $v_content, ($v_header['size'] % 512));
}
@fclose($v_dest_file);
if ($p_preserve) {
@chown($v_header['filename'], $v_header['uid']);
@chgrp($v_header['filename'], $v_header['gid']);
}
// ----- Change the file mode, mtime
@touch($v_header['filename'], $v_header['mtime']);
if ($v_header['mode'] & 0111) {
// make file executable, obey umask
$mode = fileperms($v_header['filename']) | (~umask() & 0111);
@chmod($v_header['filename'], $mode);
}
}
// ----- Check the file size
clearstatcache();
if (!is_file($v_header['filename'])) {
$this->_error(
'Extracted file ' . $v_header['filename']
. 'does not exist. Archive may be corrupted.'
);
return false;
}
$filesize = filesize($v_header['filename']);
if ($filesize != $v_header['size']) {
$this->_error(
'Extracted file ' . $v_header['filename']
. ' does not have the correct file size \''
. $filesize
. '\' (' . $v_header['size']
. ' expected). Archive may be corrupted.'
);
return false;
}
}
} else {
$this->_jumpBlock(ceil(($v_header['size'] / 512)));
}
} else {
$this->_jumpBlock(ceil(($v_header['size'] / 512)));
}
/* TBC : Seems to be unused ...
if ($this->_compress)
$v_end_of_file = @gzeof($this->_file);
else
$v_end_of_file = @feof($this->_file);
*/
if ($v_listing || $v_extract_file || $v_extraction_stopped) {
// ----- Log extracted files
if (($v_file_dir = dirname($v_header['filename']))
== $v_header['filename']
) {
$v_file_dir = '';
}
if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) {
$v_file_dir = '/';
}
$p_list_detail[$v_nb++] = $v_header;
if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) {
return true;
}
}
}
return true;
}
/**
* @return bool
*/
public function _openAppend()
{
if (filesize($this->_tarname) == 0) {
return $this->_openWrite();
}
if ($this->_compress) {
$this->_close();
if (!@rename($this->_tarname, $this->_tarname . ".tmp")) {
$this->_error(
'Error while renaming \'' . $this->_tarname
. '\' to temporary file \'' . $this->_tarname
. '.tmp\''
);
return false;
}
if ($this->_compress_type == 'gz') {
$v_temp_tar = @gzopen($this->_tarname . ".tmp", "rb");
} elseif ($this->_compress_type == 'bz2') {
$v_temp_tar = @bzopen($this->_tarname . ".tmp", "r");
} elseif ($this->_compress_type == 'lzma2') {
$v_temp_tar = @xzopen($this->_tarname . ".tmp", "r");
}
if ($v_temp_tar == 0) {
$this->_error(
'Unable to open file \'' . $this->_tarname
. '.tmp\' in binary read mode'
);
@rename($this->_tarname . ".tmp", $this->_tarname);
return false;
}
if (!$this->_openWrite()) {
@rename($this->_tarname . ".tmp", $this->_tarname);
return false;
}
if ($this->_compress_type == 'gz') {
$end_blocks = 0;
while (!@gzeof($v_temp_tar)) {
$v_buffer = @gzread($v_temp_tar, 512);
if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
$end_blocks++;
// do not copy end blocks, we will re-make them
// after appending
continue;
} elseif ($end_blocks > 0) {
for ($i = 0; $i < $end_blocks; $i++) {
$this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
}
$end_blocks = 0;
}
$v_binary_data = pack("a512", $v_buffer);
$this->_writeBlock($v_binary_data);
}
@gzclose($v_temp_tar);
} elseif ($this->_compress_type == 'bz2') {
$end_blocks = 0;
while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) {
if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
$end_blocks++;
// do not copy end blocks, we will re-make them
// after appending
continue;
} elseif ($end_blocks > 0) {
for ($i = 0; $i < $end_blocks; $i++) {
$this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
}
$end_blocks = 0;
}
$v_binary_data = pack("a512", $v_buffer);
$this->_writeBlock($v_binary_data);
}
@bzclose($v_temp_tar);
} elseif ($this->_compress_type == 'lzma2') {
$end_blocks = 0;
while (strlen($v_buffer = @xzread($v_temp_tar, 512)) > 0) {
if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) {
$end_blocks++;
// do not copy end blocks, we will re-make them
// after appending
continue;
} elseif ($end_blocks > 0) {
for ($i = 0; $i < $end_blocks; $i++) {
$this->_writeBlock(ARCHIVE_TAR_END_BLOCK);
}
$end_blocks = 0;
}
$v_binary_data = pack("a512", $v_buffer);
$this->_writeBlock($v_binary_data);
}
@xzclose($v_temp_tar);
}
if (!@unlink($this->_tarname . ".tmp")) {
$this->_error(
'Error while deleting temporary file \''
. $this->_tarname . '.tmp\''
);
}
} else {
// ----- For not compressed tar, just add files before the last
// one or two 512 bytes block
if (!$this->_openReadWrite()) {
return false;
}
clearstatcache();
$v_size = filesize($this->_tarname);
// We might have zero, one or two end blocks.
// The standard is two, but we should try to handle
// other cases.
fseek($this->_file, $v_size - 1024);
if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
fseek($this->_file, $v_size - 1024);
} elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) {
fseek($this->_file, $v_size - 512);
}
}
return true;
}
/**
* @param $p_filelist
* @param string $p_add_dir
* @param string $p_remove_dir
* @return bool
*/
public function _append($p_filelist, $p_add_dir = '', $p_remove_dir = '')
{
if (!$this->_openAppend()) {
return false;
}
if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) {
$this->_writeFooter();
}
$this->_close();
return true;
}
/**
* Check if a directory exists and create it (including parent
* dirs) if not.
*
* @param string $p_dir directory to check
*
* @return bool true if the directory exists or was created
*/
public function _dirCheck($p_dir)
{
clearstatcache();
if ((@is_dir($p_dir)) || ($p_dir == '')) {
return true;
}
$p_parent_dir = dirname($p_dir);
if (($p_parent_dir != $p_dir) &&
($p_parent_dir != '') &&
(!$this->_dirCheck($p_parent_dir))
) {
return false;
}
if (!@mkdir($p_dir, 0777)) {
$this->_error("Unable to create directory '$p_dir'");
return false;
}
return true;
}
/**
* Compress path by changing for example "/dir/foo/../bar" to "/dir/bar",
* rand emove double slashes.
*
* @param string $p_dir path to reduce
*
* @return string reduced path
*/
private function _pathReduction($p_dir)
{
$v_result = '';
// ----- Look for not empty path
if ($p_dir != '') {
// ----- Explode path by directory names
$v_list = explode('/', $p_dir);
// ----- Study directories from last to first
for ($i = sizeof($v_list) - 1; $i >= 0; $i--) {
// ----- Look for current path
if ($v_list[$i] == ".") {
// ----- Ignore this directory
// Should be the first $i=0, but no check is done
} else {
if ($v_list[$i] == "..") {
// ----- Ignore it and ignore the $i-1
$i--;
} else {
if (($v_list[$i] == '')
&& ($i != (sizeof($v_list) - 1))
&& ($i != 0)
) {
// ----- Ignore only the double '//' in path,
// but not the first and last /
} else {
$v_result = $v_list[$i] . ($i != (sizeof($v_list) - 1) ? '/'
. $v_result : '');
}
}
}
}
}
if (defined('OS_WINDOWS') && OS_WINDOWS) {
$v_result = strtr($v_result, '\\', '/');
}
return $v_result;
}
/**
* @param $p_path
* @param bool $p_remove_disk_letter
* @return string
*/
public function _translateWinPath($p_path, $p_remove_disk_letter = true)
{
if (defined('OS_WINDOWS') && OS_WINDOWS) {
// ----- Look for potential disk letter
if (($p_remove_disk_letter)
&& (($v_position = strpos($p_path, ':')) != false)
) {
$p_path = substr($p_path, $v_position + 1);
}
// ----- Change potential windows directory separator
if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0, 1) == '\\')) {
$p_path = strtr($p_path, '\\', '/');
}
}
return $p_path;
}
}
<?php
/**
* Error Stack Implementation
*
* This is an incredibly simple implementation of a very complex error handling
* facility. It contains the ability
* to track multiple errors from multiple packages simultaneously. In addition,
* it can track errors of many levels, save data along with the error, context
* information such as the exact file, line number, class and function that
* generated the error, and if necessary, it can raise a traditional PEAR_Error.
* It has built-in support for PEAR::Log, to log errors as they occur
*
* Since version 0.2alpha, it is also possible to selectively ignore errors,
* through the use of an error callback, see {@link pushCallback()}
*
* Since version 0.3alpha, it is possible to specify the exception class
* returned from {@link push()}
*
* Since version PEAR1.3.2, ErrorStack no longer instantiates an exception class. This can
* still be done quite handily in an error callback or by manipulating the returned array
* @category Debugging
* @package PEAR_ErrorStack
* @author Greg Beaver <cellog@php.net>
* @copyright 2004-2008 Greg Beaver
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR_ErrorStack
*/
/**
* Singleton storage
*
* Format:
* <pre>
* array(
* 'package1' => PEAR_ErrorStack object,
* 'package2' => PEAR_ErrorStack object,
* ...
* )
* </pre>
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_SINGLETON']
*/
$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] = array();
/**
* Global error callback (default)
*
* This is only used if set to non-false. * is the default callback for
* all packages, whereas specific packages may set a default callback
* for all instances, regardless of whether they are a singleton or not.
*
* To exclude non-singletons, only set the local callback for the singleton
* @see PEAR_ErrorStack::setDefaultCallback()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']
*/
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'] = array(
'*' => false,
);
/**
* Global Log object (default)
*
* This is only used if set to non-false. Use to set a default log object for
* all stacks, regardless of instantiation order or location
* @see PEAR_ErrorStack::setDefaultLogger()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
*/
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = false;
/**
* Global Overriding Callback
*
* This callback will override any error callbacks that specific loggers have set.
* Use with EXTREME caution
* @see PEAR_ErrorStack::staticPushCallback()
* @access private
* @global array $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']
*/
$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
/**#@+
* One of four possible return values from the error Callback
* @see PEAR_ErrorStack::_errorCallback()
*/
/**
* If this is returned, then the error will be both pushed onto the stack
* and logged.
*/
define('PEAR_ERRORSTACK_PUSHANDLOG', 1);
/**
* If this is returned, then the error will only be pushed onto the stack,
* and not logged.
*/
define('PEAR_ERRORSTACK_PUSH', 2);
/**
* If this is returned, then the error will only be logged, but not pushed
* onto the error stack.
*/
define('PEAR_ERRORSTACK_LOG', 3);
/**
* If this is returned, then the error is completely ignored.
*/
define('PEAR_ERRORSTACK_IGNORE', 4);
/**
* If this is returned, then the error is logged and die() is called.
*/
define('PEAR_ERRORSTACK_DIE', 5);
/**#@-*/
/**
* Error code for an attempt to instantiate a non-class as a PEAR_ErrorStack in
* the singleton method.
*/
define('PEAR_ERRORSTACK_ERR_NONCLASS', 1);
/**
* Error code for an attempt to pass an object into {@link PEAR_ErrorStack::getMessage()}
* that has no __toString() method
*/
define('PEAR_ERRORSTACK_ERR_OBJTOSTRING', 2);
/**
* Error Stack Implementation
*
* Usage:
* <code>
* // global error stack
* $global_stack = &PEAR_ErrorStack::singleton('MyPackage');
* // local error stack
* $local_stack = new PEAR_ErrorStack('MyPackage');
* </code>
* @author Greg Beaver <cellog@php.net>
* @version @package_version@
* @package PEAR_ErrorStack
* @category Debugging
* @copyright 2004-2008 Greg Beaver
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR_ErrorStack
*/
class PEAR_ErrorStack {
/**
* Errors are stored in the order that they are pushed on the stack.
* @since 0.4alpha Errors are no longer organized by error level.
* This renders pop() nearly unusable, and levels could be more easily
* handled in a callback anyway
* @var array
* @access private
*/
var $_errors = array();
/**
* Storage of errors by level.
*
* Allows easy retrieval and deletion of only errors from a particular level
* @since PEAR 1.4.0dev
* @var array
* @access private
*/
var $_errorsByLevel = array();
/**
* Package name this error stack represents
* @var string
* @access protected
*/
var $_package;
/**
* Determines whether a PEAR_Error is thrown upon every error addition
* @var boolean
* @access private
*/
var $_compat = false;
/**
* If set to a valid callback, this will be used to generate the error
* message from the error code, otherwise the message passed in will be
* used
* @var false|string|array
* @access private
*/
var $_msgCallback = false;
/**
* If set to a valid callback, this will be used to generate the error
* context for an error. For PHP-related errors, this will be a file
* and line number as retrieved from debug_backtrace(), but can be
* customized for other purposes. The error might actually be in a separate
* configuration file, or in a database query.
* @var false|string|array
* @access protected
*/
var $_contextCallback = false;
/**
* If set to a valid callback, this will be called every time an error
* is pushed onto the stack. The return value will be used to determine
* whether to allow an error to be pushed or logged.
*
* The return value must be one an PEAR_ERRORSTACK_* constant
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @var false|string|array
* @access protected
*/
var $_errorCallback = array();
/**
* PEAR::Log object for logging errors
* @var false|Log
* @access protected
*/
var $_logger = false;
/**
* Error messages - designed to be overridden
* @var array
* @abstract
*/
var $_errorMsgs = array();
/**
* Set up a new error stack
*
* @param string $package name of the package this error stack represents
* @param callback $msgCallback callback used for error message generation
* @param callback $contextCallback callback used for context generation,
* defaults to {@link getFileLine()}
* @param boolean $throwPEAR_Error
*/
function __construct($package, $msgCallback = false, $contextCallback = false,
$throwPEAR_Error = false)
{
$this->_package = $package;
$this->setMessageCallback($msgCallback);
$this->setContextCallback($contextCallback);
$this->_compat = $throwPEAR_Error;
}
/**
* Return a single error stack for this package.
*
* Note that all parameters are ignored if the stack for package $package
* has already been instantiated
* @param string $package name of the package this error stack represents
* @param callback $msgCallback callback used for error message generation
* @param callback $contextCallback callback used for context generation,
* defaults to {@link getFileLine()}
* @param boolean $throwPEAR_Error
* @param string $stackClass class to instantiate
*
* @return PEAR_ErrorStack
*/
public static function &singleton(
$package, $msgCallback = false, $contextCallback = false,
$throwPEAR_Error = false, $stackClass = 'PEAR_ErrorStack'
) {
if (isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
}
if (!class_exists($stackClass)) {
if (function_exists('debug_backtrace')) {
$trace = debug_backtrace();
}
PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_NONCLASS,
'exception', array('stackclass' => $stackClass),
'stack class "%stackclass%" is not a valid class name (should be like PEAR_ErrorStack)',
false, $trace);
}
$GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package] =
new $stackClass($package, $msgCallback, $contextCallback, $throwPEAR_Error);
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package];
}
/**
* Internal error handler for PEAR_ErrorStack class
*
* Dies if the error is an exception (and would have died anyway)
* @access private
*/
function _handleError($err)
{
if ($err['level'] == 'exception') {
$message = $err['message'];
if (isset($_SERVER['REQUEST_URI'])) {
echo '<br />';
} else {
echo "\n";
}
var_dump($err['context']);
die($message);
}
}
/**
* Set up a PEAR::Log object for all error stacks that don't have one
* @param Log $log
*/
public static function setDefaultLogger(&$log)
{
if (is_object($log) && method_exists($log, 'log') ) {
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
} elseif (is_callable($log)) {
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'] = &$log;
}
}
/**
* Set up a PEAR::Log object for this error stack
* @param Log $log
*/
function setLogger(&$log)
{
if (is_object($log) && method_exists($log, 'log') ) {
$this->_logger = &$log;
} elseif (is_callable($log)) {
$this->_logger = &$log;
}
}
/**
* Set an error code => error message mapping callback
*
* This method sets the callback that can be used to generate error
* messages for any instance
* @param array|string Callback function/method
*/
function setMessageCallback($msgCallback)
{
if (!$msgCallback) {
$this->_msgCallback = array(&$this, 'getErrorMessage');
} else {
if (is_callable($msgCallback)) {
$this->_msgCallback = $msgCallback;
}
}
}
/**
* Get an error code => error message mapping callback
*
* This method returns the current callback that can be used to generate error
* messages
* @return array|string|false Callback function/method or false if none
*/
function getMessageCallback()
{
return $this->_msgCallback;
}
/**
* Sets a default callback to be used by all error stacks
*
* This method sets the callback that can be used to generate error
* messages for a singleton
* @param array|string Callback function/method
* @param string Package name, or false for all packages
*/
public static function setDefaultCallback($callback = false, $package = false)
{
if (!is_callable($callback)) {
$callback = false;
}
$package = $package ? $package : '*';
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$package] = $callback;
}
/**
* Set a callback that generates context information (location of error) for an error stack
*
* This method sets the callback that can be used to generate context
* information for an error. Passing in NULL will disable context generation
* and remove the expensive call to debug_backtrace()
* @param array|string|null Callback function/method
*/
function setContextCallback($contextCallback)
{
if ($contextCallback === null) {
return $this->_contextCallback = false;
}
if (!$contextCallback) {
$this->_contextCallback = array(&$this, 'getFileLine');
} else {
if (is_callable($contextCallback)) {
$this->_contextCallback = $contextCallback;
}
}
}
/**
* Set an error Callback
* If set to a valid callback, this will be called every time an error
* is pushed onto the stack. The return value will be used to determine
* whether to allow an error to be pushed or logged.
*
* The return value must be one of the ERRORSTACK_* constants.
*
* This functionality can be used to emulate PEAR's pushErrorHandling, and
* the PEAR_ERROR_CALLBACK mode, without affecting the integrity of
* the error stack or logging
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @see popCallback()
* @param string|array $cb
*/
function pushCallback($cb)
{
array_push($this->_errorCallback, $cb);
}
/**
* Remove a callback from the error callback stack
* @see pushCallback()
* @return array|string|false
*/
function popCallback()
{
if (!count($this->_errorCallback)) {
return false;
}
return array_pop($this->_errorCallback);
}
/**
* Set a temporary overriding error callback for every package error stack
*
* Use this to temporarily disable all existing callbacks (can be used
* to emulate the @ operator, for instance)
* @see PEAR_ERRORSTACK_PUSHANDLOG, PEAR_ERRORSTACK_PUSH, PEAR_ERRORSTACK_LOG
* @see staticPopCallback(), pushCallback()
* @param string|array $cb
*/
public static function staticPushCallback($cb)
{
array_push($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'], $cb);
}
/**
* Remove a temporary overriding error callback
* @see staticPushCallback()
* @return array|string|false
*/
public static function staticPopCallback()
{
$ret = array_pop($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK']);
if (!is_array($GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'])) {
$GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = array();
}
return $ret;
}
/**
* Add an error to the stack
*
* If the message generator exists, it is called with 2 parameters.
* - the current Error Stack object
* - an array that is in the same format as an error. Available indices
* are 'code', 'package', 'time', 'params', 'level', and 'context'
*
* Next, if the error should contain context information, this is
* handled by the context grabbing method.
* Finally, the error is pushed onto the proper error stack
* @param int $code Package-specific error code
* @param string $level Error level. This is NOT spell-checked
* @param array $params associative array of error parameters
* @param string $msg Error message, or a portion of it if the message
* is to be generated
* @param array $repackage If this error re-packages an error pushed by
* another package, place the array returned from
* {@link pop()} in this parameter
* @param array $backtrace Protected parameter: use this to pass in the
* {@link debug_backtrace()} that should be used
* to find error context
* @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
* thrown. If a PEAR_Error is returned, the userinfo
* property is set to the following array:
*
* <code>
* array(
* 'code' => $code,
* 'params' => $params,
* 'package' => $this->_package,
* 'level' => $level,
* 'time' => time(),
* 'context' => $context,
* 'message' => $msg,
* //['repackage' => $err] repackaged error array/Exception class
* );
* </code>
*
* Normally, the previous array is returned.
*/
function push($code, $level = 'error', $params = array(), $msg = false,
$repackage = false, $backtrace = false)
{
$context = false;
// grab error context
if ($this->_contextCallback) {
if (!$backtrace) {
$backtrace = debug_backtrace();
}
$context = call_user_func($this->_contextCallback, $code, $params, $backtrace);
}
// save error
$time = explode(' ', microtime());
$time = $time[1] + $time[0];
$err = array(
'code' => $code,
'params' => $params,
'package' => $this->_package,
'level' => $level,
'time' => $time,
'context' => $context,
'message' => $msg,
);
if ($repackage) {
$err['repackage'] = $repackage;
}
// set up the error message, if necessary
if ($this->_msgCallback) {
$msg = call_user_func_array($this->_msgCallback,
array(&$this, $err));
$err['message'] = $msg;
}
$push = $log = true;
$die = false;
// try the overriding callback first
$callback = $this->staticPopCallback();
if ($callback) {
$this->staticPushCallback($callback);
}
if (!is_callable($callback)) {
// try the local callback next
$callback = $this->popCallback();
if (is_callable($callback)) {
$this->pushCallback($callback);
} else {
// try the default callback
$callback = isset($GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package]) ?
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK'][$this->_package] :
$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_CALLBACK']['*'];
}
}
if (is_callable($callback)) {
switch(call_user_func($callback, $err)){
case PEAR_ERRORSTACK_IGNORE:
return $err;
break;
case PEAR_ERRORSTACK_PUSH:
$log = false;
break;
case PEAR_ERRORSTACK_LOG:
$push = false;
break;
case PEAR_ERRORSTACK_DIE:
$die = true;
break;
// anything else returned has the same effect as pushandlog
}
}
if ($push) {
array_unshift($this->_errors, $err);
if (!isset($this->_errorsByLevel[$err['level']])) {
$this->_errorsByLevel[$err['level']] = array();
}
$this->_errorsByLevel[$err['level']][] = &$this->_errors[0];
}
if ($log) {
if ($this->_logger || $GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER']) {
$this->_log($err);
}
}
if ($die) {
die();
}
if ($this->_compat && $push) {
return $this->raiseError($msg, $code, null, null, $err);
}
return $err;
}
/**
* Static version of {@link push()}
*
* @param string $package Package name this error belongs to
* @param int $code Package-specific error code
* @param string $level Error level. This is NOT spell-checked
* @param array $params associative array of error parameters
* @param string $msg Error message, or a portion of it if the message
* is to be generated
* @param array $repackage If this error re-packages an error pushed by
* another package, place the array returned from
* {@link pop()} in this parameter
* @param array $backtrace Protected parameter: use this to pass in the
* {@link debug_backtrace()} that should be used
* to find error context
* @return PEAR_Error|array if compatibility mode is on, a PEAR_Error is also
* thrown. see docs for {@link push()}
*/
public static function staticPush(
$package, $code, $level = 'error', $params = array(),
$msg = false, $repackage = false, $backtrace = false
) {
$s = &PEAR_ErrorStack::singleton($package);
if ($s->_contextCallback) {
if (!$backtrace) {
if (function_exists('debug_backtrace')) {
$backtrace = debug_backtrace();
}
}
}
return $s->push($code, $level, $params, $msg, $repackage, $backtrace);
}
/**
* Log an error using PEAR::Log
* @param array $err Error array
* @param array $levels Error level => Log constant map
* @access protected
*/
function _log($err)
{
if ($this->_logger) {
$logger = &$this->_logger;
} else {
$logger = &$GLOBALS['_PEAR_ERRORSTACK_DEFAULT_LOGGER'];
}
if (is_a($logger, 'Log')) {
$levels = array(
'exception' => PEAR_LOG_CRIT,
'alert' => PEAR_LOG_ALERT,
'critical' => PEAR_LOG_CRIT,
'error' => PEAR_LOG_ERR,
'warning' => PEAR_LOG_WARNING,
'notice' => PEAR_LOG_NOTICE,
'info' => PEAR_LOG_INFO,
'debug' => PEAR_LOG_DEBUG);
if (isset($levels[$err['level']])) {
$level = $levels[$err['level']];
} else {
$level = PEAR_LOG_INFO;
}
$logger->log($err['message'], $level, $err);
} else { // support non-standard logs
call_user_func($logger, $err);
}
}
/**
* Pop an error off of the error stack
*
* @return false|array
* @since 0.4alpha it is no longer possible to specify a specific error
* level to return - the last error pushed will be returned, instead
*/
function pop()
{
$err = @array_shift($this->_errors);
if (!is_null($err)) {
@array_pop($this->_errorsByLevel[$err['level']]);
if (!count($this->_errorsByLevel[$err['level']])) {
unset($this->_errorsByLevel[$err['level']]);
}
}
return $err;
}
/**
* Pop an error off of the error stack, static method
*
* @param string package name
* @return boolean
* @since PEAR1.5.0a1
*/
static function staticPop($package)
{
if ($package) {
if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return false;
}
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->pop();
}
}
/**
* Determine whether there are any errors on the stack
* @param string|array Level name. Use to determine if any errors
* of level (string), or levels (array) have been pushed
* @return boolean
*/
function hasErrors($level = false)
{
if ($level) {
return isset($this->_errorsByLevel[$level]);
}
return count($this->_errors);
}
/**
* Retrieve all errors since last purge
*
* @param boolean set in order to empty the error stack
* @param string level name, to return only errors of a particular severity
* @return array
*/
function getErrors($purge = false, $level = false)
{
if (!$purge) {
if ($level) {
if (!isset($this->_errorsByLevel[$level])) {
return array();
} else {
return $this->_errorsByLevel[$level];
}
} else {
return $this->_errors;
}
}
if ($level) {
$ret = $this->_errorsByLevel[$level];
foreach ($this->_errorsByLevel[$level] as $i => $unused) {
// entries are references to the $_errors array
$this->_errorsByLevel[$level][$i] = false;
}
// array_filter removes all entries === false
$this->_errors = array_filter($this->_errors);
unset($this->_errorsByLevel[$level]);
return $ret;
}
$ret = $this->_errors;
$this->_errors = array();
$this->_errorsByLevel = array();
return $ret;
}
/**
* Determine whether there are any errors on a single error stack, or on any error stack
*
* The optional parameter can be used to test the existence of any errors without the need of
* singleton instantiation
* @param string|false Package name to check for errors
* @param string Level name to check for a particular severity
* @return boolean
*/
public static function staticHasErrors($package = false, $level = false)
{
if ($package) {
if (!isset($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package])) {
return false;
}
return $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->hasErrors($level);
}
foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
if ($obj->hasErrors($level)) {
return true;
}
}
return false;
}
/**
* Get a list of all errors since last purge, organized by package
* @since PEAR 1.4.0dev BC break! $level is now in the place $merge used to be
* @param boolean $purge Set to purge the error stack of existing errors
* @param string $level Set to a level name in order to retrieve only errors of a particular level
* @param boolean $merge Set to return a flat array, not organized by package
* @param array $sortfunc Function used to sort a merged array - default
* sorts by time, and should be good for most cases
*
* @return array
*/
public static function staticGetErrors(
$purge = false, $level = false, $merge = false,
$sortfunc = array('PEAR_ErrorStack', '_sortErrors')
) {
$ret = array();
if (!is_callable($sortfunc)) {
$sortfunc = array('PEAR_ErrorStack', '_sortErrors');
}
foreach ($GLOBALS['_PEAR_ERRORSTACK_SINGLETON'] as $package => $obj) {
$test = $GLOBALS['_PEAR_ERRORSTACK_SINGLETON'][$package]->getErrors($purge, $level);
if ($test) {
if ($merge) {
$ret = array_merge($ret, $test);
} else {
$ret[$package] = $test;
}
}
}
if ($merge) {
usort($ret, $sortfunc);
}
return $ret;
}
/**
* Error sorting function, sorts by time
* @access private
*/
public static function _sortErrors($a, $b)
{
if ($a['time'] == $b['time']) {
return 0;
}
if ($a['time'] < $b['time']) {
return 1;
}
return -1;
}
/**
* Standard file/line number/function/class context callback
*
* This function uses a backtrace generated from {@link debug_backtrace()}
* and so will not work at all in PHP < 4.3.0. The frame should
* reference the frame that contains the source of the error.
* @return array|false either array('file' => file, 'line' => line,
* 'function' => function name, 'class' => class name) or
* if this doesn't work, then false
* @param unused
* @param integer backtrace frame.
* @param array Results of debug_backtrace()
*/
public static function getFileLine($code, $params, $backtrace = null)
{
if ($backtrace === null) {
return false;
}
$frame = 0;
$functionframe = 1;
if (!isset($backtrace[1])) {
$functionframe = 0;
} else {
while (isset($backtrace[$functionframe]['function']) &&
$backtrace[$functionframe]['function'] == 'eval' &&
isset($backtrace[$functionframe + 1])) {
$functionframe++;
}
}
if (isset($backtrace[$frame])) {
if (!isset($backtrace[$frame]['file'])) {
$frame++;
}
$funcbacktrace = $backtrace[$functionframe];
$filebacktrace = $backtrace[$frame];
$ret = array('file' => $filebacktrace['file'],
'line' => $filebacktrace['line']);
// rearrange for eval'd code or create function errors
if (strpos($filebacktrace['file'], '(') &&
preg_match(';^(.*?)\((\d+)\) : (.*?)\\z;', $filebacktrace['file'],
$matches)) {
$ret['file'] = $matches[1];
$ret['line'] = $matches[2] + 0;
}
if (isset($funcbacktrace['function']) && isset($backtrace[1])) {
if ($funcbacktrace['function'] != 'eval') {
if ($funcbacktrace['function'] == '__lambda_func') {
$ret['function'] = 'create_function() code';
} else {
$ret['function'] = $funcbacktrace['function'];
}
}
}
if (isset($funcbacktrace['class']) && isset($backtrace[1])) {
$ret['class'] = $funcbacktrace['class'];
}
return $ret;
}
return false;
}
/**
* Standard error message generation callback
*
* This method may also be called by a custom error message generator
* to fill in template values from the params array, simply
* set the third parameter to the error message template string to use
*
* The special variable %__msg% is reserved: use it only to specify
* where a message passed in by the user should be placed in the template,
* like so:
*
* Error message: %msg% - internal error
*
* If the message passed like so:
*
* <code>
* $stack->push(ERROR_CODE, 'error', array(), 'server error 500');
* </code>
*
* The returned error message will be "Error message: server error 500 -
* internal error"
* @param PEAR_ErrorStack
* @param array
* @param string|false Pre-generated error message template
*
* @return string
*/
public static function getErrorMessage(&$stack, $err, $template = false)
{
if ($template) {
$mainmsg = $template;
} else {
$mainmsg = $stack->getErrorMessageTemplate($err['code']);
}
$mainmsg = str_replace('%__msg%', $err['message'], $mainmsg);
if (is_array($err['params']) && count($err['params'])) {
foreach ($err['params'] as $name => $val) {
if (is_array($val)) {
// @ is needed in case $val is a multi-dimensional array
$val = @implode(', ', $val);
}
if (is_object($val)) {
if (method_exists($val, '__toString')) {
$val = $val->__toString();
} else {
PEAR_ErrorStack::staticPush('PEAR_ErrorStack', PEAR_ERRORSTACK_ERR_OBJTOSTRING,
'warning', array('obj' => get_class($val)),
'object %obj% passed into getErrorMessage, but has no __toString() method');
$val = 'Object';
}
}
$mainmsg = str_replace('%' . $name . '%', $val, $mainmsg);
}
}
return $mainmsg;
}
/**
* Standard Error Message Template generator from code
* @return string
*/
function getErrorMessageTemplate($code)
{
if (!isset($this->_errorMsgs[$code])) {
return '%__msg%';
}
return $this->_errorMsgs[$code];
}
/**
* Set the Error Message Template array
*
* The array format must be:
* <pre>
* array(error code => 'message template',...)
* </pre>
*
* Error message parameters passed into {@link push()} will be used as input
* for the error message. If the template is 'message %foo% was %bar%', and the
* parameters are array('foo' => 'one', 'bar' => 'six'), the error message returned will
* be 'message one was six'
* @return string
*/
function setErrorMessageTemplate($template)
{
$this->_errorMsgs = $template;
}
/**
* emulate PEAR::raiseError()
*
* @return PEAR_Error
*/
function raiseError()
{
require_once 'PEAR.php';
$args = func_get_args();
return call_user_func_array(array('PEAR', 'raiseError'), $args);
}
}
$stack = &PEAR_ErrorStack::singleton('PEAR_ErrorStack');
$stack->pushCallback(array('PEAR_ErrorStack', '_handleError'));
?>
<?php
/**
* Dummy file to make autoloaders work
*
* PHP version 5
*
* @category PEAR
* @package PEAR
* @author Christian Weiske <cweiske@php.net>
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR
*/
require_once __DIR__ . '/../PEAR.php';
?><?php
/**
* File/Directory manipulation
*
* PHP versions 4 and 5
*
* @category pear
* @package System
* @author Tomas V.V.Cox <cox@idecnet.com>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR
* @since File available since Release 0.1
*/
/**
* base class
*/
require_once 'PEAR.php';
require_once 'Console/Getopt.php';
$GLOBALS['_System_temp_files'] = array();
/**
* System offers cross platform compatible system functions
*
* Static functions for different operations. Should work under
* Unix and Windows. The names and usage has been taken from its respectively
* GNU commands. The functions will return (bool) false on error and will
* trigger the error with the PHP trigger_error() function (you can silence
* the error by prefixing a '@' sign after the function call, but this
* is not recommended practice. Instead use an error handler with
* {@link set_error_handler()}).
*
* Documentation on this class you can find in:
* http://pear.php.net/manual/
*
* Example usage:
* if (!@System::rm('-r file1 dir1')) {
* print "could not delete file1 or dir1";
* }
*
* In case you need to to pass file names with spaces,
* pass the params as an array:
*
* System::rm(array('-r', $file1, $dir1));
*
* @category pear
* @package System
* @author Tomas V.V. Cox <cox@idecnet.com>
* @copyright 1997-2006 The PHP Group
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: @package_version@
* @link http://pear.php.net/package/PEAR
* @since Class available since Release 0.1
* @static
*/
class System
{
/**
* returns the commandline arguments of a function
*
* @param string $argv the commandline
* @param string $short_options the allowed option short-tags
* @param string $long_options the allowed option long-tags
* @return array the given options and there values
*/
public static function _parseArgs($argv, $short_options, $long_options = null)
{
if (!is_array($argv) && $argv !== null) {
/*
// Quote all items that are a short option
$av = preg_split('/(\A| )--?[a-z0-9]+[ =]?((?<!\\\\)((,\s*)|((?<!,)\s+))?)/i', $argv, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
$offset = 0;
foreach ($av as $a) {
$b = trim($a[0]);
if ($b[0] == '"' || $b[0] == "'") {
continue;
}
$escape = escapeshellarg($b);
$pos = $a[1] + $offset;
$argv = substr_replace($argv, $escape, $pos, strlen($b));
$offset += 2;
}
*/
// Find all items, quoted or otherwise
preg_match_all("/(?:[\"'])(.*?)(?:['\"])|([^\s]+)/", $argv, $av);
$argv = $av[1];
foreach ($av[2] as $k => $a) {
if (empty($a)) {
continue;
}
$argv[$k] = trim($a) ;
}
}
return Console_Getopt::getopt2($argv, $short_options, $long_options);
}
/**
* Output errors with PHP trigger_error(). You can silence the errors
* with prefixing a "@" sign to the function call: @System::mkdir(..);
*
* @param mixed $error a PEAR error or a string with the error message
* @return bool false
*/
protected static function raiseError($error)
{
if (PEAR::isError($error)) {
$error = $error->getMessage();
}
trigger_error($error, E_USER_WARNING);
return false;
}
/**
* Creates a nested array representing the structure of a directory
*
* System::_dirToStruct('dir1', 0) =>
* Array
* (
* [dirs] => Array
* (
* [0] => dir1
* )
*
* [files] => Array
* (
* [0] => dir1/file2
* [1] => dir1/file3
* )
* )
* @param string $sPath Name of the directory
* @param integer $maxinst max. deep of the lookup
* @param integer $aktinst starting deep of the lookup
* @param bool $silent if true, do not emit errors.
* @return array the structure of the dir
*/
protected static function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false)
{
$struct = array('dirs' => array(), 'files' => array());
if (($dir = @opendir($sPath)) === false) {
if (!$silent) {
System::raiseError("Could not open dir $sPath");
}
return $struct; // XXX could not open error
}
$struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ?
$list = array();
while (false !== ($file = readdir($dir))) {
if ($file != '.' && $file != '..') {
$list[] = $file;
}
}
closedir($dir);
natsort($list);
if ($aktinst < $maxinst || $maxinst == 0) {
foreach ($list as $val) {
$path = $sPath . DIRECTORY_SEPARATOR . $val;
if (is_dir($path) && !is_link($path)) {
$tmp = System::_dirToStruct($path, $maxinst, $aktinst+1, $silent);
$struct = array_merge_recursive($struct, $tmp);
} else {
$struct['files'][] = $path;
}
}
}
return $struct;
}
/**
* Creates a nested array representing the structure of a directory and files
*
* @param array $files Array listing files and dirs
* @return array
* @static
* @see System::_dirToStruct()
*/
protected static function _multipleToStruct($files)
{
$struct = array('dirs' => array(), 'files' => array());
settype($files, 'array');
foreach ($files as $file) {
if (is_dir($file) && !is_link($file)) {
$tmp = System::_dirToStruct($file, 0);
$struct = array_merge_recursive($tmp, $struct);
} else {
if (!in_array($file, $struct['files'])) {
$struct['files'][] = $file;
}
}
}
return $struct;
}
/**
* The rm command for removing files.
* Supports multiple files and dirs and also recursive deletes
*
* @param string $args the arguments for rm
* @return mixed PEAR_Error or true for success
* @static
* @access public
*/
public static function rm($args)
{
$opts = System::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-)
if (PEAR::isError($opts)) {
return System::raiseError($opts);
}
foreach ($opts[0] as $opt) {
if ($opt[0] == 'r') {
$do_recursive = true;
}
}
$ret = true;
if (isset($do_recursive)) {
$struct = System::_multipleToStruct($opts[1]);
foreach ($struct['files'] as $file) {
if (!@unlink($file)) {
$ret = false;
}
}
rsort($struct['dirs']);
foreach ($struct['dirs'] as $dir) {
if (!@rmdir($dir)) {
$ret = false;
}
}
} else {
foreach ($opts[1] as $file) {
$delete = (is_dir($file)) ? 'rmdir' : 'unlink';
if (!@$delete($file)) {
$ret = false;
}
}
}
return $ret;
}
/**
* Make directories.
*
* The -p option will create parent directories
* @param string $args the name of the director(y|ies) to create
* @return bool True for success
*/
public static function mkDir($args)
{
$opts = System::_parseArgs($args, 'pm:');
if (PEAR::isError($opts)) {
return System::raiseError($opts);
}
$mode = 0777; // default mode
foreach ($opts[0] as $opt) {
if ($opt[0] == 'p') {
$create_parents = true;
} elseif ($opt[0] == 'm') {
// if the mode is clearly an octal number (starts with 0)
// convert it to decimal
if (strlen($opt[1]) && $opt[1][0] == '0') {
$opt[1] = octdec($opt[1]);
} else {
// convert to int
$opt[1] += 0;
}
$mode = $opt[1];
}
}
$ret = true;
if (isset($create_parents)) {
foreach ($opts[1] as $dir) {
$dirstack = array();
while ((!file_exists($dir) || !is_dir($dir)) &&
$dir != DIRECTORY_SEPARATOR) {
array_unshift($dirstack, $dir);
$dir = dirname($dir);
}
while ($newdir = array_shift($dirstack)) {
if (!is_writeable(dirname($newdir))) {
$ret = false;
break;
}
if (!mkdir($newdir, $mode)) {
$ret = false;
}
}
}
} else {
foreach($opts[1] as $dir) {
if ((@file_exists($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) {
$ret = false;
}
}
}
return $ret;
}
/**
* Concatenate files
*
* Usage:
* 1) $var = System::cat('sample.txt test.txt');
* 2) System::cat('sample.txt test.txt > final.txt');
* 3) System::cat('sample.txt test.txt >> final.txt');
*
* Note: as the class use fopen, urls should work also
*
* @param string $args the arguments
* @return boolean true on success
*/
public static function &cat($args)
{
$ret = null;
$files = array();
if (!is_array($args)) {
$args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
}
$count_args = count($args);
for ($i = 0; $i < $count_args; $i++) {
if ($args[$i] == '>') {
$mode = 'wb';
$outputfile = $args[$i+1];
break;
} elseif ($args[$i] == '>>') {
$mode = 'ab+';
$outputfile = $args[$i+1];
break;
} else {
$files[] = $args[$i];
}
}
$outputfd = false;
if (isset($mode)) {
if (!$outputfd = fopen($outputfile, $mode)) {
$err = System::raiseError("Could not open $outputfile");
return $err;
}
$ret = true;
}
foreach ($files as $file) {
if (!$fd = fopen($file, 'r')) {
System::raiseError("Could not open $file");
continue;
}
while ($cont = fread($fd, 2048)) {
if (is_resource($outputfd)) {
fwrite($outputfd, $cont);
} else {
$ret .= $cont;
}
}
fclose($fd);
}
if (is_resource($outputfd)) {
fclose($outputfd);
}
return $ret;
}
/**
* Creates temporary files or directories. This function will remove
* the created files when the scripts finish its execution.
*
* Usage:
* 1) $tempfile = System::mktemp("prefix");
* 2) $tempdir = System::mktemp("-d prefix");
* 3) $tempfile = System::mktemp();
* 4) $tempfile = System::mktemp("-t /var/tmp prefix");
*
* prefix -> The string that will be prepended to the temp name
* (defaults to "tmp").
* -d -> A temporary dir will be created instead of a file.
* -t -> The target dir where the temporary (file|dir) will be created. If
* this param is missing by default the env vars TMP on Windows or
* TMPDIR in Unix will be used. If these vars are also missing
* c:\windows\temp or /tmp will be used.
*
* @param string $args The arguments
* @return mixed the full path of the created (file|dir) or false
* @see System::tmpdir()
*/
public static function mktemp($args = null)
{
static $first_time = true;
$opts = System::_parseArgs($args, 't:d');
if (PEAR::isError($opts)) {
return System::raiseError($opts);
}
foreach ($opts[0] as $opt) {
if ($opt[0] == 'd') {
$tmp_is_dir = true;
} elseif ($opt[0] == 't') {
$tmpdir = $opt[1];
}
}
$prefix = (isset($opts[1][0])) ? $opts[1][0] : 'tmp';
if (!isset($tmpdir)) {
$tmpdir = System::tmpdir();
}
if (!System::mkDir(array('-p', $tmpdir))) {
return false;
}
$tmp = tempnam($tmpdir, $prefix);
if (isset($tmp_is_dir)) {
unlink($tmp); // be careful possible race condition here
if (!mkdir($tmp, 0700)) {
return System::raiseError("Unable to create temporary directory $tmpdir");
}
}
$GLOBALS['_System_temp_files'][] = $tmp;
if (isset($tmp_is_dir)) {
//$GLOBALS['_System_temp_files'][] = dirname($tmp);
}
if ($first_time) {
PEAR::registerShutdownFunc(array('System', '_removeTmpFiles'));
$first_time = false;
}
return $tmp;
}
/**
* Remove temporary files created my mkTemp. This function is executed
* at script shutdown time
*/
public static function _removeTmpFiles()
{
if (count($GLOBALS['_System_temp_files'])) {
$delete = $GLOBALS['_System_temp_files'];
array_unshift($delete, '-r');
System::rm($delete);
$GLOBALS['_System_temp_files'] = array();
}
}
/**
* Get the path of the temporal directory set in the system
* by looking in its environments variables.
* Note: php.ini-recommended removes the "E" from the variables_order setting,
* making unavaible the $_ENV array, that s why we do tests with _ENV
*
* @return string The temporary directory on the system
*/
public static function tmpdir()
{
if (OS_WINDOWS) {
if ($var = isset($_ENV['TMP']) ? $_ENV['TMP'] : getenv('TMP')) {
return $var;
}
if ($var = isset($_ENV['TEMP']) ? $_ENV['TEMP'] : getenv('TEMP')) {
return $var;
}
if ($var = isset($_ENV['USERPROFILE']) ? $_ENV['USERPROFILE'] : getenv('USERPROFILE')) {
return $var;
}
if ($var = isset($_ENV['windir']) ? $_ENV['windir'] : getenv('windir')) {
return $var;
}
return getenv('SystemRoot') . '\temp';
}
if ($var = isset($_ENV['TMPDIR']) ? $_ENV['TMPDIR'] : getenv('TMPDIR')) {
return $var;
}
return realpath(function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp');
}
/**
* The "which" command (show the full path of a command)
*
* @param string $program The command to search for
* @param mixed $fallback Value to return if $program is not found
*
* @return mixed A string with the full path or false if not found
* @author Stig Bakken <ssb@php.net>
*/
public static function which($program, $fallback = false)
{
// enforce API
if (!is_string($program) || '' == $program) {
return $fallback;
}
// full path given
if (basename($program) != $program) {
$path_elements[] = dirname($program);
$program = basename($program);
} else {
$path = getenv('PATH');
if (!$path) {
$path = getenv('Path'); // some OSes are just stupid enough to do this
}
$path_elements = explode(PATH_SEPARATOR, $path);
}
if (OS_WINDOWS) {
$exe_suffixes = getenv('PATHEXT')
? explode(PATH_SEPARATOR, getenv('PATHEXT'))
: array('.exe','.bat','.cmd','.com');
// allow passing a command.exe param
if (strpos($program, '.') !== false) {
array_unshift($exe_suffixes, '');
}
} else {
$exe_suffixes = array('');
}
foreach ($exe_suffixes as $suff) {
foreach ($path_elements as $dir) {
$file = $dir . DIRECTORY_SEPARATOR . $program . $suff;
// It's possible to run a .bat on Windows that is_executable
// would return false for. The is_executable check is meaningless...
if (OS_WINDOWS) {
return $file;
} else {
if (is_executable($file)) {
return $file;
}
}
}
}
return $fallback;
}
/**
* The "find" command
*
* Usage:
*
* System::find($dir);
* System::find("$dir -type d");
* System::find("$dir -type f");
* System::find("$dir -name *.php");
* System::find("$dir -name *.php -name *.htm*");
* System::find("$dir -maxdepth 1");
*
* Params implemented:
* $dir -> Start the search at this directory
* -type d -> return only directories
* -type f -> return only files
* -maxdepth <n> -> max depth of recursion
* -name <pattern> -> search pattern (bash style). Multiple -name param allowed
*
* @param mixed Either array or string with the command line
* @return array Array of found files
*/
public static function find($args)
{
if (!is_array($args)) {
$args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
}
$dir = realpath(array_shift($args));
if (!$dir) {
return array();
}
$patterns = array();
$depth = 0;
$do_files = $do_dirs = true;
$args_count = count($args);
for ($i = 0; $i < $args_count; $i++) {
switch ($args[$i]) {
case '-type':
if (in_array($args[$i+1], array('d', 'f'))) {
if ($args[$i+1] == 'd') {
$do_files = false;
} else {
$do_dirs = false;
}
}
$i++;
break;
case '-name':
$name = preg_quote($args[$i+1], '#');
// our magic characters ? and * have just been escaped,
// so now we change the escaped versions to PCRE operators
$name = strtr($name, array('\?' => '.', '\*' => '.*'));
$patterns[] = '('.$name.')';
$i++;
break;
case '-maxdepth':
$depth = $args[$i+1];
break;
}
}
$path = System::_dirToStruct($dir, $depth, 0, true);
if ($do_files && $do_dirs) {
$files = array_merge($path['files'], $path['dirs']);
} elseif ($do_dirs) {
$files = $path['dirs'];
} else {
$files = $path['files'];
}
if (count($patterns)) {
$dsq = preg_quote(DIRECTORY_SEPARATOR, '#');
$pattern = '#(^|'.$dsq.')'.implode('|', $patterns).'($|'.$dsq.')#';
$ret = array();
$files_count = count($files);
for ($i = 0; $i < $files_count; $i++) {
// only search in the part of the file below the current directory
$filepart = basename($files[$i]);
if (preg_match($pattern, $filepart)) {
$ret[] = $files[$i];
}
}
return $ret;
}
return $files;
}
}
<?php
/**
* PEAR, the PHP Extension and Application Repository
*
* PEAR class and PEAR_Error class
*
* PHP versions 4 and 5
*
* @category pear
* @package PEAR
* @author Sterling Hughes <sterling@php.net>
* @author Stig Bakken <ssb@php.net>
* @author Tomas V.V.Cox <cox@idecnet.com>
* @author Greg Beaver <cellog@php.net>
* @copyright 1997-2010 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR
* @since File available since Release 0.1
*/
/**#@+
* ERROR constants
*/
define('PEAR_ERROR_RETURN', 1);
define('PEAR_ERROR_PRINT', 2);
define('PEAR_ERROR_TRIGGER', 4);
define('PEAR_ERROR_DIE', 8);
define('PEAR_ERROR_CALLBACK', 16);
/**
* WARNING: obsolete
* @deprecated
*/
define('PEAR_ERROR_EXCEPTION', 32);
/**#@-*/
if (substr(PHP_OS, 0, 3) == 'WIN') {
define('OS_WINDOWS', true);
define('OS_UNIX', false);
define('PEAR_OS', 'Windows');
} else {
define('OS_WINDOWS', false);
define('OS_UNIX', true);
define('PEAR_OS', 'Unix'); // blatant assumption
}
$GLOBALS['_PEAR_default_error_mode'] = PEAR_ERROR_RETURN;
$GLOBALS['_PEAR_default_error_options'] = E_USER_NOTICE;
$GLOBALS['_PEAR_destructor_object_list'] = array();
$GLOBALS['_PEAR_shutdown_funcs'] = array();
$GLOBALS['_PEAR_error_handler_stack'] = array();
@ini_set('track_errors', true);
/**
* Base class for other PEAR classes. Provides rudimentary
* emulation of destructors.
*
* If you want a destructor in your class, inherit PEAR and make a
* destructor method called _yourclassname (same name as the
* constructor, but with a "_" prefix). Also, in your constructor you
* have to call the PEAR constructor: $this->PEAR();.
* The destructor method will be called without parameters. Note that
* at in some SAPI implementations (such as Apache), any output during
* the request shutdown (in which destructors are called) seems to be
* discarded. If you need to get any debug information from your
* destructor, use error_log(), syslog() or something similar.
*
* IMPORTANT! To use the emulated destructors you need to create the
* objects by reference: $obj =& new PEAR_child;
*
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Tomas V.V. Cox <cox@idecnet.com>
* @author Greg Beaver <cellog@php.net>
* @copyright 1997-2006 The PHP Group
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: @package_version@
* @link http://pear.php.net/package/PEAR
* @see PEAR_Error
* @since Class available since PHP 4.0.2
* @link http://pear.php.net/manual/en/core.pear.php#core.pear.pear
*/
class PEAR
{
/**
* Whether to enable internal debug messages.
*
* @var bool
* @access private
*/
var $_debug = false;
/**
* Default error mode for this object.
*
* @var int
* @access private
*/
var $_default_error_mode = null;
/**
* Default error options used for this object when error mode
* is PEAR_ERROR_TRIGGER.
*
* @var int
* @access private
*/
var $_default_error_options = null;
/**
* Default error handler (callback) for this object, if error mode is
* PEAR_ERROR_CALLBACK.
*
* @var string
* @access private
*/
var $_default_error_handler = '';
/**
* Which class to use for error objects.
*
* @var string
* @access private
*/
var $_error_class = 'PEAR_Error';
/**
* An array of expected errors.
*
* @var array
* @access private
*/
var $_expected_errors = array();
/**
* List of methods that can be called both statically and non-statically.
* @var array
*/
protected static $bivalentMethods = array(
'setErrorHandling' => true,
'raiseError' => true,
'throwError' => true,
'pushErrorHandling' => true,
'popErrorHandling' => true,
);
/**
* Constructor. Registers this object in
* $_PEAR_destructor_object_list for destructor emulation if a
* destructor object exists.
*
* @param string $error_class (optional) which class to use for
* error objects, defaults to PEAR_Error.
* @access public
* @return void
*/
function __construct($error_class = null)
{
$classname = strtolower(get_class($this));
if ($this->_debug) {
print "PEAR constructor called, class=$classname\n";
}
if ($error_class !== null) {
$this->_error_class = $error_class;
}
while ($classname && strcasecmp($classname, "pear")) {
$destructor = "_$classname";
if (method_exists($this, $destructor)) {
global $_PEAR_destructor_object_list;
$_PEAR_destructor_object_list[] = $this;
if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
register_shutdown_function("_PEAR_call_destructors");
$GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
}
break;
} else {
$classname = get_parent_class($classname);
}
}
}
/**
* Only here for backwards compatibility.
* E.g. Archive_Tar calls $this->PEAR() in its constructor.
*
* @param string $error_class Which class to use for error objects,
* defaults to PEAR_Error.
*/
public function PEAR($error_class = null)
{
self::__construct($error_class);
}
/**
* Destructor (the emulated type of...). Does nothing right now,
* but is included for forward compatibility, so subclass
* destructors should always call it.
*
* See the note in the class desciption about output from
* destructors.
*
* @access public
* @return void
*/
function _PEAR() {
if ($this->_debug) {
printf("PEAR destructor called, class=%s\n", strtolower(get_class($this)));
}
}
public function __call($method, $arguments)
{
if (!isset(self::$bivalentMethods[$method])) {
trigger_error(
'Call to undefined method PEAR::' . $method . '()', E_USER_ERROR
);
}
return call_user_func_array(
array(get_class(), '_' . $method),
array_merge(array($this), $arguments)
);
}
public static function __callStatic($method, $arguments)
{
if (!isset(self::$bivalentMethods[$method])) {
trigger_error(
'Call to undefined method PEAR::' . $method . '()', E_USER_ERROR
);
}
return call_user_func_array(
array(get_class(), '_' . $method),
array_merge(array(null), $arguments)
);
}
/**
* If you have a class that's mostly/entirely static, and you need static
* properties, you can use this method to simulate them. Eg. in your method(s)
* do this: $myVar = &PEAR::getStaticProperty('myclass', 'myVar');
* You MUST use a reference, or they will not persist!
*
* @param string $class The calling classname, to prevent clashes
* @param string $var The variable to retrieve.
* @return mixed A reference to the variable. If not set it will be
* auto initialised to NULL.
*/
public static function &getStaticProperty($class, $var)
{
static $properties;
if (!isset($properties[$class])) {
$properties[$class] = array();
}
if (!array_key_exists($var, $properties[$class])) {
$properties[$class][$var] = null;
}
return $properties[$class][$var];
}
/**
* Use this function to register a shutdown method for static
* classes.
*
* @param mixed $func The function name (or array of class/method) to call
* @param mixed $args The arguments to pass to the function
*
* @return void
*/
public static function registerShutdownFunc($func, $args = array())
{
// if we are called statically, there is a potential
// that no shutdown func is registered. Bug #6445
if (!isset($GLOBALS['_PEAR_SHUTDOWN_REGISTERED'])) {
register_shutdown_function("_PEAR_call_destructors");
$GLOBALS['_PEAR_SHUTDOWN_REGISTERED'] = true;
}
$GLOBALS['_PEAR_shutdown_funcs'][] = array($func, $args);
}
/**
* Tell whether a value is a PEAR error.
*
* @param mixed $data the value to test
* @param int $code if $data is an error object, return true
* only if $code is a string and
* $obj->getMessage() == $code or
* $code is an integer and $obj->getCode() == $code
*
* @return bool true if parameter is an error
*/
public static function isError($data, $code = null)
{
if (!is_a($data, 'PEAR_Error')) {
return false;
}
if (is_null($code)) {
return true;
} elseif (is_string($code)) {
return $data->getMessage() == $code;
}
return $data->getCode() == $code;
}
/**
* Sets how errors generated by this object should be handled.
* Can be invoked both in objects and statically. If called
* statically, setErrorHandling sets the default behaviour for all
* PEAR objects. If called in an object, setErrorHandling sets
* the default behaviour for that object.
*
* @param object $object
* Object the method was called on (non-static mode)
*
* @param int $mode
* One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
* PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
* PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION.
*
* @param mixed $options
* When $mode is PEAR_ERROR_TRIGGER, this is the error level (one
* of E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
*
* When $mode is PEAR_ERROR_CALLBACK, this parameter is expected
* to be the callback function or method. A callback
* function is a string with the name of the function, a
* callback method is an array of two elements: the element
* at index 0 is the object, and the element at index 1 is
* the name of the method to call in the object.
*
* When $mode is PEAR_ERROR_PRINT or PEAR_ERROR_DIE, this is
* a printf format string used when printing the error
* message.
*
* @access public
* @return void
* @see PEAR_ERROR_RETURN
* @see PEAR_ERROR_PRINT
* @see PEAR_ERROR_TRIGGER
* @see PEAR_ERROR_DIE
* @see PEAR_ERROR_CALLBACK
* @see PEAR_ERROR_EXCEPTION
*
* @since PHP 4.0.5
*/
protected static function _setErrorHandling(
$object, $mode = null, $options = null
) {
if ($object !== null) {
$setmode = &$object->_default_error_mode;
$setoptions = &$object->_default_error_options;
} else {
$setmode = &$GLOBALS['_PEAR_default_error_mode'];
$setoptions = &$GLOBALS['_PEAR_default_error_options'];
}
switch ($mode) {
case PEAR_ERROR_EXCEPTION:
case PEAR_ERROR_RETURN:
case PEAR_ERROR_PRINT:
case PEAR_ERROR_TRIGGER:
case PEAR_ERROR_DIE:
case null:
$setmode = $mode;
$setoptions = $options;
break;
case PEAR_ERROR_CALLBACK:
$setmode = $mode;
// class/object method callback
if (is_callable($options)) {
$setoptions = $options;
} else {
trigger_error("invalid error callback", E_USER_WARNING);
}
break;
default:
trigger_error("invalid error mode", E_USER_WARNING);
break;
}
}
/**
* This method is used to tell which errors you expect to get.
* Expected errors are always returned with error mode
* PEAR_ERROR_RETURN. Expected error codes are stored in a stack,
* and this method pushes a new element onto it. The list of
* expected errors are in effect until they are popped off the
* stack with the popExpect() method.
*
* Note that this method can not be called statically
*
* @param mixed $code a single error code or an array of error codes to expect
*
* @return int the new depth of the "expected errors" stack
* @access public
*/
function expectError($code = '*')
{
if (is_array($code)) {
array_push($this->_expected_errors, $code);
} else {
array_push($this->_expected_errors, array($code));
}
return count($this->_expected_errors);
}
/**
* This method pops one element off the expected error codes
* stack.
*
* @return array the list of error codes that were popped
*/
function popExpect()
{
return array_pop($this->_expected_errors);
}
/**
* This method checks unsets an error code if available
*
* @param mixed error code
* @return bool true if the error code was unset, false otherwise
* @access private
* @since PHP 4.3.0
*/
function _checkDelExpect($error_code)
{
$deleted = false;
foreach ($this->_expected_errors as $key => $error_array) {
if (in_array($error_code, $error_array)) {
unset($this->_expected_errors[$key][array_search($error_code, $error_array)]);
$deleted = true;
}
// clean up empty arrays
if (0 == count($this->_expected_errors[$key])) {
unset($this->_expected_errors[$key]);
}
}
return $deleted;
}
/**
* This method deletes all occurrences of the specified element from
* the expected error codes stack.
*
* @param mixed $error_code error code that should be deleted
* @return mixed list of error codes that were deleted or error
* @access public
* @since PHP 4.3.0
*/
function delExpect($error_code)
{
$deleted = false;
if ((is_array($error_code) && (0 != count($error_code)))) {
// $error_code is a non-empty array here; we walk through it trying
// to unset all values
foreach ($error_code as $key => $error) {
$deleted = $this->_checkDelExpect($error) ? true : false;
}
return $deleted ? true : PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
} elseif (!empty($error_code)) {
// $error_code comes alone, trying to unset it
if ($this->_checkDelExpect($error_code)) {
return true;
}
return PEAR::raiseError("The expected error you submitted does not exist"); // IMPROVE ME
}
// $error_code is empty
return PEAR::raiseError("The expected error you submitted is empty"); // IMPROVE ME
}
/**
* This method is a wrapper that returns an instance of the
* configured error class with this object's default error
* handling applied. If the $mode and $options parameters are not
* specified, the object's defaults are used.
*
* @param mixed $message a text error message or a PEAR error object
*
* @param int $code a numeric error code (it is up to your class
* to define these if you want to use codes)
*
* @param int $mode One of PEAR_ERROR_RETURN, PEAR_ERROR_PRINT,
* PEAR_ERROR_TRIGGER, PEAR_ERROR_DIE,
* PEAR_ERROR_CALLBACK, PEAR_ERROR_EXCEPTION.
*
* @param mixed $options If $mode is PEAR_ERROR_TRIGGER, this parameter
* specifies the PHP-internal error level (one of
* E_USER_NOTICE, E_USER_WARNING or E_USER_ERROR).
* If $mode is PEAR_ERROR_CALLBACK, this
* parameter specifies the callback function or
* method. In other error modes this parameter
* is ignored.
*
* @param string $userinfo If you need to pass along for example debug
* information, this parameter is meant for that.
*
* @param string $error_class The returned error object will be
* instantiated from this class, if specified.
*
* @param bool $skipmsg If true, raiseError will only pass error codes,
* the error message parameter will be dropped.
*
* @return object a PEAR error object
* @see PEAR::setErrorHandling
* @since PHP 4.0.5
*/
protected static function _raiseError($object,
$message = null,
$code = null,
$mode = null,
$options = null,
$userinfo = null,
$error_class = null,
$skipmsg = false)
{
// The error is yet a PEAR error object
if (is_object($message)) {
$code = $message->getCode();
$userinfo = $message->getUserInfo();
$error_class = $message->getType();
$message->error_message_prefix = '';
$message = $message->getMessage();
}
if (
$object !== null &&
isset($object->_expected_errors) &&
count($object->_expected_errors) > 0 &&
count($exp = end($object->_expected_errors))
) {
if ($exp[0] === "*" ||
(is_int(reset($exp)) && in_array($code, $exp)) ||
(is_string(reset($exp)) && in_array($message, $exp))
) {
$mode = PEAR_ERROR_RETURN;
}
}
// No mode given, try global ones
if ($mode === null) {
// Class error handler
if ($object !== null && isset($object->_default_error_mode)) {
$mode = $object->_default_error_mode;
$options = $object->_default_error_options;
// Global error handler
} elseif (isset($GLOBALS['_PEAR_default_error_mode'])) {
$mode = $GLOBALS['_PEAR_default_error_mode'];
$options = $GLOBALS['_PEAR_default_error_options'];
}
}
if ($error_class !== null) {
$ec = $error_class;
} elseif ($object !== null && isset($object->_error_class)) {
$ec = $object->_error_class;
} else {
$ec = 'PEAR_Error';
}
if ($skipmsg) {
$a = new $ec($code, $mode, $options, $userinfo);
} else {
$a = new $ec($message, $code, $mode, $options, $userinfo);
}
return $a;
}
/**
* Simpler form of raiseError with fewer options. In most cases
* message, code and userinfo are enough.
*
* @param mixed $message a text error message or a PEAR error object
*
* @param int $code a numeric error code (it is up to your class
* to define these if you want to use codes)
*
* @param string $userinfo If you need to pass along for example debug
* information, this parameter is meant for that.
*
* @return object a PEAR error object
* @see PEAR::raiseError
*/
protected static function _throwError($object, $message = null, $code = null, $userinfo = null)
{
if ($object !== null) {
$a = $object->raiseError($message, $code, null, null, $userinfo);
return $a;
}
$a = PEAR::raiseError($message, $code, null, null, $userinfo);
return $a;
}
public static function staticPushErrorHandling($mode, $options = null)
{
$stack = &$GLOBALS['_PEAR_error_handler_stack'];
$def_mode = &$GLOBALS['_PEAR_default_error_mode'];
$def_options = &$GLOBALS['_PEAR_default_error_options'];
$stack[] = array($def_mode, $def_options);
switch ($mode) {
case PEAR_ERROR_EXCEPTION:
case PEAR_ERROR_RETURN:
case PEAR_ERROR_PRINT:
case PEAR_ERROR_TRIGGER:
case PEAR_ERROR_DIE:
case null:
$def_mode = $mode;
$def_options = $options;
break;
case PEAR_ERROR_CALLBACK:
$def_mode = $mode;
// class/object method callback
if (is_callable($options)) {
$def_options = $options;
} else {
trigger_error("invalid error callback", E_USER_WARNING);
}
break;
default:
trigger_error("invalid error mode", E_USER_WARNING);
break;
}
$stack[] = array($mode, $options);
return true;
}
public static function staticPopErrorHandling()
{
$stack = &$GLOBALS['_PEAR_error_handler_stack'];
$setmode = &$GLOBALS['_PEAR_default_error_mode'];
$setoptions = &$GLOBALS['_PEAR_default_error_options'];
array_pop($stack);
list($mode, $options) = $stack[sizeof($stack) - 1];
array_pop($stack);
switch ($mode) {
case PEAR_ERROR_EXCEPTION:
case PEAR_ERROR_RETURN:
case PEAR_ERROR_PRINT:
case PEAR_ERROR_TRIGGER:
case PEAR_ERROR_DIE:
case null:
$setmode = $mode;
$setoptions = $options;
break;
case PEAR_ERROR_CALLBACK:
$setmode = $mode;
// class/object method callback
if (is_callable($options)) {
$setoptions = $options;
} else {
trigger_error("invalid error callback", E_USER_WARNING);
}
break;
default:
trigger_error("invalid error mode", E_USER_WARNING);
break;
}
return true;
}
/**
* Push a new error handler on top of the error handler options stack. With this
* you can easily override the actual error handler for some code and restore
* it later with popErrorHandling.
*
* @param mixed $mode (same as setErrorHandling)
* @param mixed $options (same as setErrorHandling)
*
* @return bool Always true
*
* @see PEAR::setErrorHandling
*/
protected static function _pushErrorHandling($object, $mode, $options = null)
{
$stack = &$GLOBALS['_PEAR_error_handler_stack'];
if ($object !== null) {
$def_mode = &$object->_default_error_mode;
$def_options = &$object->_default_error_options;
} else {
$def_mode = &$GLOBALS['_PEAR_default_error_mode'];
$def_options = &$GLOBALS['_PEAR_default_error_options'];
}
$stack[] = array($def_mode, $def_options);
if ($object !== null) {
$object->setErrorHandling($mode, $options);
} else {
PEAR::setErrorHandling($mode, $options);
}
$stack[] = array($mode, $options);
return true;
}
/**
* Pop the last error handler used
*
* @return bool Always true
*
* @see PEAR::pushErrorHandling
*/
protected static function _popErrorHandling($object)
{
$stack = &$GLOBALS['_PEAR_error_handler_stack'];
array_pop($stack);
list($mode, $options) = $stack[sizeof($stack) - 1];
array_pop($stack);
if ($object !== null) {
$object->setErrorHandling($mode, $options);
} else {
PEAR::setErrorHandling($mode, $options);
}
return true;
}
/**
* OS independent PHP extension load. Remember to take care
* on the correct extension name for case sensitive OSes.
*
* @param string $ext The extension name
* @return bool Success or not on the dl() call
*/
public static function loadExtension($ext)
{
if (extension_loaded($ext)) {
return true;
}
// if either returns true dl() will produce a FATAL error, stop that
if (
function_exists('dl') === false ||
ini_get('enable_dl') != 1
) {
return false;
}
if (OS_WINDOWS) {
$suffix = '.dll';
} elseif (PHP_OS == 'HP-UX') {
$suffix = '.sl';
} elseif (PHP_OS == 'AIX') {
$suffix = '.a';
} elseif (PHP_OS == 'OSX') {
$suffix = '.bundle';
} else {
$suffix = '.so';
}
return @dl('php_'.$ext.$suffix) || @dl($ext.$suffix);
}
/**
* Get SOURCE_DATE_EPOCH environment variable
* See https://reproducible-builds.org/specs/source-date-epoch/
*
* @return int
* @access public
*/
static function getSourceDateEpoch()
{
if ($source_date_epoch = getenv('SOURCE_DATE_EPOCH')) {
if (preg_match('/^\d+$/', $source_date_epoch)) {
return (int) $source_date_epoch;
} else {
// "If the value is malformed, the build process SHOULD exit with a non-zero error code."
self::raiseError("Invalid SOURCE_DATE_EPOCH: $source_date_epoch");
exit(1);
}
} else {
return time();
}
}
}
function _PEAR_call_destructors()
{
global $_PEAR_destructor_object_list;
if (is_array($_PEAR_destructor_object_list) &&
sizeof($_PEAR_destructor_object_list))
{
reset($_PEAR_destructor_object_list);
$destructLifoExists = PEAR::getStaticProperty('PEAR', 'destructlifo');
if ($destructLifoExists) {
$_PEAR_destructor_object_list = array_reverse($_PEAR_destructor_object_list);
}
foreach ($_PEAR_destructor_object_list as $k => $objref) {
$classname = get_class($objref);
while ($classname) {
$destructor = "_$classname";
if (method_exists($objref, $destructor)) {
$objref->$destructor();
break;
} else {
$classname = get_parent_class($classname);
}
}
}
// Empty the object list to ensure that destructors are
// not called more than once.
$_PEAR_destructor_object_list = array();
}
// Now call the shutdown functions
if (
isset($GLOBALS['_PEAR_shutdown_funcs']) &&
is_array($GLOBALS['_PEAR_shutdown_funcs']) &&
!empty($GLOBALS['_PEAR_shutdown_funcs'])
) {
foreach ($GLOBALS['_PEAR_shutdown_funcs'] as $value) {
call_user_func_array($value[0], $value[1]);
}
}
}
/**
* Standard PEAR error class for PHP 4
*
* This class is supserseded by {@link PEAR_Exception} in PHP 5
*
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Tomas V.V. Cox <cox@idecnet.com>
* @author Gregory Beaver <cellog@php.net>
* @copyright 1997-2006 The PHP Group
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: @package_version@
* @link http://pear.php.net/manual/en/core.pear.pear-error.php
* @see PEAR::raiseError(), PEAR::throwError()
* @since Class available since PHP 4.0.2
*/
class PEAR_Error
{
var $error_message_prefix = '';
var $mode = PEAR_ERROR_RETURN;
var $level = E_USER_NOTICE;
var $code = -1;
var $message = '';
var $userinfo = '';
var $backtrace = null;
/**
* PEAR_Error constructor
*
* @param string $message message
*
* @param int $code (optional) error code
*
* @param int $mode (optional) error mode, one of: PEAR_ERROR_RETURN,
* PEAR_ERROR_PRINT, PEAR_ERROR_DIE, PEAR_ERROR_TRIGGER,
* PEAR_ERROR_CALLBACK or PEAR_ERROR_EXCEPTION
*
* @param mixed $options (optional) error level, _OR_ in the case of
* PEAR_ERROR_CALLBACK, the callback function or object/method
* tuple.
*
* @param string $userinfo (optional) additional user/debug info
*
* @access public
*
*/
function __construct($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null)
{
if ($mode === null) {
$mode = PEAR_ERROR_RETURN;
}
$this->message = $message;
$this->code = $code;
$this->mode = $mode;
$this->userinfo = $userinfo;
$skiptrace = PEAR::getStaticProperty('PEAR_Error', 'skiptrace');
if (!$skiptrace) {
$this->backtrace = debug_backtrace();
if (isset($this->backtrace[0]) && isset($this->backtrace[0]['object'])) {
unset($this->backtrace[0]['object']);
}
}
if ($mode & PEAR_ERROR_CALLBACK) {
$this->level = E_USER_NOTICE;
$this->callback = $options;
} else {
if ($options === null) {
$options = E_USER_NOTICE;
}
$this->level = $options;
$this->callback = null;
}
if ($this->mode & PEAR_ERROR_PRINT) {
if (is_null($options) || is_int($options)) {
$format = "%s";
} else {
$format = $options;
}
printf($format, $this->getMessage());
}
if ($this->mode & PEAR_ERROR_TRIGGER) {
trigger_error($this->getMessage(), $this->level);
}
if ($this->mode & PEAR_ERROR_DIE) {
$msg = $this->getMessage();
if (is_null($options) || is_int($options)) {
$format = "%s";
if (substr($msg, -1) != "\n") {
$msg .= "\n";
}
} else {
$format = $options;
}
printf($format, $msg);
exit($code);
}
if ($this->mode & PEAR_ERROR_CALLBACK && is_callable($this->callback)) {
call_user_func($this->callback, $this);
}
if ($this->mode & PEAR_ERROR_EXCEPTION) {
trigger_error("PEAR_ERROR_EXCEPTION is obsolete, use class PEAR_Exception for exceptions", E_USER_WARNING);
eval('$e = new Exception($this->message, $this->code);throw($e);');
}
}
/**
* Only here for backwards compatibility.
*
* Class "Cache_Error" still uses it, among others.
*
* @param string $message Message
* @param int $code Error code
* @param int $mode Error mode
* @param mixed $options See __construct()
* @param string $userinfo Additional user/debug info
*/
public function PEAR_Error(
$message = 'unknown error', $code = null, $mode = null,
$options = null, $userinfo = null
) {
self::__construct($message, $code, $mode, $options, $userinfo);
}
/**
* Get the error mode from an error object.
*
* @return int error mode
* @access public
*/
function getMode()
{
return $this->mode;
}
/**
* Get the callback function/method from an error object.
*
* @return mixed callback function or object/method array
* @access public
*/
function getCallback()
{
return $this->callback;
}
/**
* Get the error message from an error object.
*
* @return string full error message
* @access public
*/
function getMessage()
{
return ($this->error_message_prefix . $this->message);
}
/**
* Get error code from an error object
*
* @return int error code
* @access public
*/
function getCode()
{
return $this->code;
}
/**
* Get the name of this error/exception.
*
* @return string error/exception name (type)
* @access public
*/
function getType()
{
return get_class($this);
}
/**
* Get additional user-supplied information.
*
* @return string user-supplied information
* @access public
*/
function getUserInfo()
{
return $this->userinfo;
}
/**
* Get additional debug information supplied by the application.
*
* @return string debug information
* @access public
*/
function getDebugInfo()
{
return $this->getUserInfo();
}
/**
* Get the call backtrace from where the error was generated.
* Supported with PHP 4.3.0 or newer.
*
* @param int $frame (optional) what frame to fetch
* @return array Backtrace, or NULL if not available.
* @access public
*/
function getBacktrace($frame = null)
{
if (defined('PEAR_IGNORE_BACKTRACE')) {
return null;
}
if ($frame === null) {
return $this->backtrace;
}
return $this->backtrace[$frame];
}
function addUserInfo($info)
{
if (empty($this->userinfo)) {
$this->userinfo = $info;
} else {
$this->userinfo .= " ** $info";
}
}
function __toString()
{
return $this->getMessage();
}
/**
* Make a string representation of this object.
*
* @return string a string with an object summary
* @access public
*/
function toString()
{
$modes = array();
$levels = array(E_USER_NOTICE => 'notice',
E_USER_WARNING => 'warning',
E_USER_ERROR => 'error');
if ($this->mode & PEAR_ERROR_CALLBACK) {
if (is_array($this->callback)) {
$callback = (is_object($this->callback[0]) ?
strtolower(get_class($this->callback[0])) :
$this->callback[0]) . '::' .
$this->callback[1];
} else {
$callback = $this->callback;
}
return sprintf('[%s: message="%s" code=%d mode=callback '.
'callback=%s prefix="%s" info="%s"]',
strtolower(get_class($this)), $this->message, $this->code,
$callback, $this->error_message_prefix,
$this->userinfo);
}
if ($this->mode & PEAR_ERROR_PRINT) {
$modes[] = 'print';
}
if ($this->mode & PEAR_ERROR_TRIGGER) {
$modes[] = 'trigger';
}
if ($this->mode & PEAR_ERROR_DIE) {
$modes[] = 'die';
}
if ($this->mode & PEAR_ERROR_RETURN) {
$modes[] = 'return';
}
return sprintf('[%s: message="%s" code=%d mode=%s level=%s '.
'prefix="%s" info="%s"]',
strtolower(get_class($this)), $this->message, $this->code,
implode("|", $modes), $levels[$this->level],
$this->error_message_prefix,
$this->userinfo);
}
}
/*
* Local Variables:
* mode: php
* tab-width: 4
* c-basic-offset: 4
* End:
*/
<?php
/**
* The OS_Guess class
*
* PHP versions 4 and 5
*
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Gregory Beaver <cellog@php.net>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR
* @since File available since PEAR 0.1
*/
// {{{ uname examples
// php_uname() without args returns the same as 'uname -a', or a PHP-custom
// string for Windows.
// PHP versions prior to 4.3 return the uname of the host where PHP was built,
// as of 4.3 it returns the uname of the host running the PHP code.
//
// PC RedHat Linux 7.1:
// Linux host.example.com 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown
//
// PC Debian Potato:
// Linux host 2.4.17 #2 SMP Tue Feb 12 15:10:04 CET 2002 i686 unknown
//
// PC FreeBSD 3.3:
// FreeBSD host.example.com 3.3-STABLE FreeBSD 3.3-STABLE #0: Mon Feb 21 00:42:31 CET 2000 root@example.com:/usr/src/sys/compile/CONFIG i386
//
// PC FreeBSD 4.3:
// FreeBSD host.example.com 4.3-RELEASE FreeBSD 4.3-RELEASE #1: Mon Jun 25 11:19:43 EDT 2001 root@example.com:/usr/src/sys/compile/CONFIG i386
//
// PC FreeBSD 4.5:
// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb 6 23:59:23 CET 2002 root@example.com:/usr/src/sys/compile/CONFIG i386
//
// PC FreeBSD 4.5 w/uname from GNU shellutils:
// FreeBSD host.example.com 4.5-STABLE FreeBSD 4.5-STABLE #0: Wed Feb i386 unknown
//
// HP 9000/712 HP-UX 10:
// HP-UX iq B.10.10 A 9000/712 2008429113 two-user license
//
// HP 9000/712 HP-UX 10 w/uname from GNU shellutils:
// HP-UX host B.10.10 A 9000/712 unknown
//
// IBM RS6000/550 AIX 4.3:
// AIX host 3 4 000003531C00
//
// AIX 4.3 w/uname from GNU shellutils:
// AIX host 3 4 000003531C00 unknown
//
// SGI Onyx IRIX 6.5 w/uname from GNU shellutils:
// IRIX64 host 6.5 01091820 IP19 mips
//
// SGI Onyx IRIX 6.5:
// IRIX64 host 6.5 01091820 IP19
//
// SparcStation 20 Solaris 8 w/uname from GNU shellutils:
// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc
//
// SparcStation 20 Solaris 8:
// SunOS host.example.com 5.8 Generic_108528-12 sun4m sparc SUNW,SPARCstation-20
//
// Mac OS X (Darwin)
// Darwin home-eden.local 7.5.0 Darwin Kernel Version 7.5.0: Thu Aug 5 19:26:16 PDT 2004; root:xnu/xnu-517.7.21.obj~3/RELEASE_PPC Power Macintosh
//
// Mac OS X early versions
//
// }}}
/* TODO:
* - define endianness, to allow matchSignature("bigend") etc.
*/
/**
* Retrieves information about the current operating system
*
* This class uses php_uname() to grok information about the current OS
*
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Gregory Beaver <cellog@php.net>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: @package_version@
* @link http://pear.php.net/package/PEAR
* @since Class available since Release 0.1
*/
class OS_Guess
{
var $sysname;
var $nodename;
var $cpu;
var $release;
var $extra;
function __construct($uname = null)
{
list($this->sysname,
$this->release,
$this->cpu,
$this->extra,
$this->nodename) = $this->parseSignature($uname);
}
function parseSignature($uname = null)
{
static $sysmap = array(
'HP-UX' => 'hpux',
'IRIX64' => 'irix',
);
static $cpumap = array(
'i586' => 'i386',
'i686' => 'i386',
'ppc' => 'powerpc',
);
if ($uname === null) {
$uname = php_uname();
}
$parts = preg_split('/\s+/', trim($uname));
$n = count($parts);
$release = $machine = $cpu = '';
$sysname = $parts[0];
$nodename = $parts[1];
$cpu = $parts[$n-1];
$extra = '';
if ($cpu == 'unknown') {
$cpu = $parts[$n - 2];
}
switch ($sysname) {
case 'AIX' :
$release = "$parts[3].$parts[2]";
break;
case 'Windows' :
switch ($parts[1]) {
case '95/98':
$release = '9x';
break;
default:
$release = $parts[1];
break;
}
$cpu = 'i386';
break;
case 'Linux' :
$extra = $this->_detectGlibcVersion();
// use only the first two digits from the kernel version
$release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]);
break;
case 'Mac' :
$sysname = 'darwin';
$nodename = $parts[2];
$release = $parts[3];
if ($cpu == 'Macintosh') {
if ($parts[$n - 2] == 'Power') {
$cpu = 'powerpc';
}
}
break;
case 'Darwin' :
if ($cpu == 'Macintosh') {
if ($parts[$n - 2] == 'Power') {
$cpu = 'powerpc';
}
}
$release = preg_replace('/^([0-9]+\.[0-9]+).*/', '\1', $parts[2]);
break;
default:
$release = preg_replace('/-.*/', '', $parts[2]);
break;
}
if (isset($sysmap[$sysname])) {
$sysname = $sysmap[$sysname];
} else {
$sysname = strtolower($sysname);
}
if (isset($cpumap[$cpu])) {
$cpu = $cpumap[$cpu];
}
return array($sysname, $release, $cpu, $extra, $nodename);
}
function _detectGlibcVersion()
{
static $glibc = false;
if ($glibc !== false) {
return $glibc; // no need to run this multiple times
}
$major = $minor = 0;
include_once "System.php";
if (@is_link('/lib64/libc.so.6')) {
// Let's try reading the libc.so.6 symlink
if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib64/libc.so.6')), $matches)) {
list($major, $minor) = explode('.', $matches[1]);
}
} else if (@is_link('/lib/libc.so.6')) {
// Let's try reading the libc.so.6 symlink
if (preg_match('/^libc-(.*)\.so$/', basename(readlink('/lib/libc.so.6')), $matches)) {
list($major, $minor) = explode('.', $matches[1]);
}
}
// Use glibc's <features.h> header file to
// get major and minor version number:
if (!($major && $minor) &&
@file_exists('/usr/include/features.h') &&
@is_readable('/usr/include/features.h')) {
if (!@file_exists('/usr/bin/cpp') || !@is_executable('/usr/bin/cpp')) {
$features_file = fopen('/usr/include/features.h', 'rb');
while (!feof($features_file)) {
$line = fgets($features_file, 8192);
if (!$line || (strpos($line, '#define') === false)) {
continue;
}
if (strpos($line, '__GLIBC__')) {
// major version number #define __GLIBC__ version
$line = preg_split('/\s+/', $line);
$glibc_major = trim($line[2]);
if (isset($glibc_minor)) {
break;
}
continue;
}
if (strpos($line, '__GLIBC_MINOR__')) {
// got the minor version number
// #define __GLIBC_MINOR__ version
$line = preg_split('/\s+/', $line);
$glibc_minor = trim($line[2]);
if (isset($glibc_major)) {
break;
}
continue;
}
}
fclose($features_file);
if (!isset($glibc_major) || !isset($glibc_minor)) {
return $glibc = '';
}
return $glibc = 'glibc' . trim($glibc_major) . "." . trim($glibc_minor) ;
} // no cpp
$tmpfile = System::mktemp("glibctest");
$fp = fopen($tmpfile, "w");
fwrite($fp, "#include <features.h>\n__GLIBC__ __GLIBC_MINOR__\n");
fclose($fp);
$cpp = popen("/usr/bin/cpp $tmpfile", "r");
while ($line = fgets($cpp, 1024)) {
if ($line[0] == '#' || trim($line) == '') {
continue;
}
if (list($major, $minor) = explode(' ', trim($line))) {
break;
}
}
pclose($cpp);
unlink($tmpfile);
} // features.h
if (!($major && $minor)) {
return $glibc = '';
}
return $glibc = "glibc{$major}.{$minor}";
}
function getSignature()
{
if (empty($this->extra)) {
return "{$this->sysname}-{$this->release}-{$this->cpu}";
}
return "{$this->sysname}-{$this->release}-{$this->cpu}-{$this->extra}";
}
function getSysname()
{
return $this->sysname;
}
function getNodename()
{
return $this->nodename;
}
function getCpu()
{
return $this->cpu;
}
function getRelease()
{
return $this->release;
}
function getExtra()
{
return $this->extra;
}
function matchSignature($match)
{
$fragments = is_array($match) ? $match : explode('-', $match);
$n = count($fragments);
$matches = 0;
if ($n > 0) {
$matches += $this->_matchFragment($fragments[0], $this->sysname);
}
if ($n > 1) {
$matches += $this->_matchFragment($fragments[1], $this->release);
}
if ($n > 2) {
$matches += $this->_matchFragment($fragments[2], $this->cpu);
}
if ($n > 3) {
$matches += $this->_matchFragment($fragments[3], $this->extra);
}
return ($matches == $n);
}
function _matchFragment($fragment, $value)
{
if (strcspn($fragment, '*?') < strlen($fragment)) {
$reg = '/^' . str_replace(array('*', '?', '/'), array('.*', '.', '\\/'), $fragment) . '\\z/';
return preg_match($reg, $value);
}
return ($fragment == '*' || !strcasecmp($fragment, $value));
}
}
/*
* Local Variables:
* indent-tabs-mode: nil
* c-basic-offset: 4
* End:
*/
<?php
/**
* CssMin - A (simple) css minifier with benefits
*
* --
* Copyright (c) 2011 Joe Scylla <joe.scylla@gmail.com>
*
* 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:
*
* 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,
* 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.
* --
*
* @package CssMin
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
/**
* Abstract definition of a CSS token class.
*
* Every token has to extend this class.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssToken
{
/**
* Returns the token as string.
*
* @return string
*/
abstract public function __toString();
}
/**
* Abstract definition of a for a ruleset start token.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssRulesetStartToken extends aCssToken
{
}
/**
* Abstract definition of a for ruleset end token.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssRulesetEndToken extends aCssToken
{
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "}";
}
}
/**
* Abstract definition of a parser plugin.
*
* Every parser plugin have to extend this class. A parser plugin contains the logic to parse one or aspects of a
* stylesheet.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssParserPlugin
{
/**
* Plugin configuration.
*
* @var array
*/
protected $configuration = array();
/**
* The CssParser of the plugin.
*
* @var CssParser
*/
protected $parser = null;
/**
* Plugin buffer.
*
* @var string
*/
protected $buffer = "";
/**
* Constructor.
*
* @param CssParser $parser The CssParser object of this plugin.
* @param array $configuration Plugin configuration [optional]
* @return void
*/
public function __construct(CssParser $parser, array $configuration = null)
{
$this->configuration = $configuration;
$this->parser = $parser;
}
/**
* Returns the array of chars triggering the parser plugin.
*
* @return array
*/
abstract public function getTriggerChars();
/**
* Returns the array of states triggering the parser plugin or FALSE if every state will trigger the parser plugin.
*
* @return array
*/
abstract public function getTriggerStates();
/**
* Parser routine of the plugin.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
abstract public function parse($index, $char, $previousChar, $state);
}
/**
* Abstract definition of a minifier plugin class.
*
* Minifier plugin process the parsed tokens one by one to apply changes to the token. Every minifier plugin has to
* extend this class.
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssMinifierPlugin
{
/**
* Plugin configuration.
*
* @var array
*/
protected $configuration = array();
/**
* The CssMinifier of the plugin.
*
* @var CssMinifier
*/
protected $minifier = null;
/**
* Constructor.
*
* @param CssMinifier $minifier The CssMinifier object of this plugin.
* @param array $configuration Plugin configuration [optional]
* @return void
*/
public function __construct(CssMinifier $minifier, array $configuration = array())
{
$this->configuration = $configuration;
$this->minifier = $minifier;
}
/**
* Apply the plugin to the token.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
abstract public function apply(aCssToken &$token);
/**
* --
*
* @return array
*/
abstract public function getTriggerTokens();
}
/**
* Abstract definition of a minifier filter class.
*
* Minifier filters allows a pre-processing of the parsed token to add, edit or delete tokens. Every minifier filter
* has to extend this class.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssMinifierFilter
{
/**
* Filter configuration.
*
* @var array
*/
protected $configuration = array();
/**
* The CssMinifier of the filter.
*
* @var CssMinifier
*/
protected $minifier = null;
/**
* Constructor.
*
* @param CssMinifier $minifier The CssMinifier object of this plugin.
* @param array $configuration Filter configuration [optional]
* @return void
*/
public function __construct(CssMinifier $minifier, array $configuration = array())
{
$this->configuration = $configuration;
$this->minifier = $minifier;
}
/**
* Filter the tokens.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
abstract public function apply(array &$tokens);
}
/**
* Abstract formatter definition.
*
* Every formatter have to extend this class.
*
* @package CssMin/Formatter
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssFormatter
{
/**
* Indent string.
*
* @var string
*/
protected $indent = " ";
/**
* Declaration padding.
*
* @var integer
*/
protected $padding = 0;
/**
* Tokens.
*
* @var array
*/
protected $tokens = array();
/**
* Constructor.
*
* @param array $tokens Array of CssToken
* @param string $indent Indent string [optional]
* @param integer $padding Declaration value padding [optional]
*/
public function __construct(array $tokens, $indent = null, $padding = null)
{
$this->tokens = $tokens;
$this->indent = !is_null($indent) ? $indent : $this->indent;
$this->padding = !is_null($padding) ? $padding : $this->padding;
}
/**
* Returns the array of aCssToken as formatted string.
*
* @return string
*/
abstract public function __toString();
}
/**
* Abstract definition of a ruleset declaration token.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssDeclarationToken extends aCssToken
{
/**
* Is the declaration flagged as important?
*
* @var boolean
*/
public $IsImportant = false;
/**
* Is the declaration flagged as last one of the ruleset?
*
* @var boolean
*/
public $IsLast = false;
/**
* Property name of the declaration.
*
* @var string
*/
public $Property = "";
/**
* Value of the declaration.
*
* @var string
*/
public $Value = "";
/**
* Set the properties of the @font-face declaration.
*
* @param string $property Property of the declaration
* @param string $value Value of the declaration
* @param boolean $isImportant Is the !important flag is set?
* @param boolean $IsLast Is the declaration the last one of the block?
* @return void
*/
public function __construct($property, $value, $isImportant = false, $isLast = false)
{
$this->Property = $property;
$this->Value = $value;
$this->IsImportant = $isImportant;
$this->IsLast = $isLast;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return $this->Property . ":" . $this->Value . ($this->IsImportant ? " !important" : "") . ($this->IsLast ? "" : ";");
}
}
/**
* Abstract definition of a for at-rule block start token.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssAtBlockStartToken extends aCssToken
{
}
/**
* Abstract definition of a for at-rule block end token.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
abstract class aCssAtBlockEndToken extends aCssToken
{
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "}";
}
}
/**
* {@link aCssFromatter Formatter} returning the CSS source in {@link http://goo.gl/etzLs Whitesmiths indent style}.
*
* @package CssMin/Formatter
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssWhitesmithsFormatter extends aCssFormatter
{
/**
* Implements {@link aCssFormatter::__toString()}.
*
* @return string
*/
public function __toString()
{
$r = array();
$level = 0;
for ($i = 0, $l = count($this->tokens); $i < $l; $i++)
{
$token = $this->tokens[$i];
$class = get_class($token);
$indent = str_repeat($this->indent, $level);
if ($class === "CssCommentToken")
{
$lines = array_map("trim", explode("\n", $token->Comment));
for ($ii = 0, $ll = count($lines); $ii < $ll; $ii++)
{
$r[] = $indent . (substr($lines[$ii], 0, 1) == "*" ? " " : "") . $lines[$ii];
}
}
elseif ($class === "CssAtCharsetToken")
{
$r[] = $indent . "@charset " . $token->Charset . ";";
}
elseif ($class === "CssAtFontFaceStartToken")
{
$r[] = $indent . "@font-face";
$r[] = $this->indent . $indent . "{";
$level++;
}
elseif ($class === "CssAtImportToken")
{
$r[] = $indent . "@import " . $token->Import . " " . implode(", ", $token->MediaTypes) . ";";
}
elseif ($class === "CssAtKeyframesStartToken")
{
$r[] = $indent . "@keyframes " . $token->Name;
$r[] = $this->indent . $indent . "{";
$level++;
}
elseif ($class === "CssAtMediaStartToken")
{
$r[] = $indent . "@media " . implode(", ", $token->MediaTypes);
$r[] = $this->indent . $indent . "{";
$level++;
}
elseif ($class === "CssAtPageStartToken")
{
$r[] = $indent . "@page";
$r[] = $this->indent . $indent . "{";
$level++;
}
elseif ($class === "CssAtVariablesStartToken")
{
$r[] = $indent . "@variables " . implode(", ", $token->MediaTypes);
$r[] = $this->indent . $indent . "{";
$level++;
}
elseif ($class === "CssRulesetStartToken" || $class === "CssAtKeyframesRulesetStartToken")
{
$r[] = $indent . implode(", ", $token->Selectors);
$r[] = $this->indent . $indent . "{";
$level++;
}
elseif ($class === "CssAtFontFaceDeclarationToken"
|| $class === "CssAtKeyframesRulesetDeclarationToken"
|| $class === "CssAtPageDeclarationToken"
|| $class === "CssAtVariablesDeclarationToken"
|| $class === "CssRulesetDeclarationToken"
)
{
$declaration = $indent . $token->Property . ": ";
if ($this->padding)
{
$declaration = str_pad($declaration, $this->padding, " ", STR_PAD_RIGHT);
}
$r[] = $declaration . $token->Value . ($token->IsImportant ? " !important" : "") . ";";
}
elseif ($class === "CssAtFontFaceEndToken"
|| $class === "CssAtMediaEndToken"
|| $class === "CssAtKeyframesEndToken"
|| $class === "CssAtKeyframesRulesetEndToken"
|| $class === "CssAtPageEndToken"
|| $class === "CssAtVariablesEndToken"
|| $class === "CssRulesetEndToken"
)
{
$r[] = $indent . "}";
$level--;
}
}
return implode("\n", $r);
}
}
/**
* This {@link aCssMinifierPlugin} will process var-statement and sets the declaration value to the variable value.
*
* This plugin only apply the variable values. The variable values itself will get parsed by the
* {@link CssVariablesMinifierFilter}.
*
* Example:
* <code>
* @variables
* {
* defaultColor: black;
* }
* color: var(defaultColor);
* </code>
*
* Will get converted to:
* <code>
* color:black;
* </code>
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssVariablesMinifierPlugin extends aCssMinifierPlugin
{
/**
* Regular expression matching a value.
*
* @var string
*/
private $reMatch = "/var\((.+)\)/iSU";
/**
* Parsed variables.
*
* @var array
*/
private $variables = null;
/**
* Returns the variables.
*
* @return array
*/
public function getVariables()
{
return $this->variables;
}
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (stripos($token->Value, "var") !== false && preg_match_all($this->reMatch, $token->Value, $m))
{
$mediaTypes = $token->MediaTypes;
if (!in_array("all", $mediaTypes))
{
$mediaTypes[] = "all";
}
for ($i = 0, $l = count($m[0]); $i < $l; $i++)
{
$variable = trim($m[1][$i]);
foreach ($mediaTypes as $mediaType)
{
if (isset($this->variables[$mediaType], $this->variables[$mediaType][$variable]))
{
// Variable value found => set the declaration value to the variable value and return
$token->Value = str_replace($m[0][$i], $this->variables[$mediaType][$variable], $token->Value);
continue 2;
}
}
// If no value was found trigger an error and replace the token with a CssNullToken
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": No value found for variable <code>" . $variable . "</code> in media types <code>" . implode(", ", $mediaTypes) . "</code>", (string) $token));
$token = new CssNullToken();
return true;
}
}
return false;
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
/**
* Sets the variables.
*
* @param array $variables Variables to set
* @return void
*/
public function setVariables(array $variables)
{
$this->variables = $variables;
}
}
/**
* This {@link aCssMinifierFilter minifier filter} will parse the variable declarations out of @variables at-rule
* blocks. The variables will get store in the {@link CssVariablesMinifierPlugin} that will apply the variables to
* declaration.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssVariablesMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$variables = array();
$defaultMediaTypes = array("all");
$mediaTypes = array();
$remove = array();
for($i = 0, $l = count($tokens); $i < $l; $i++)
{
// @variables at-rule block found
if (get_class($tokens[$i]) === "CssAtVariablesStartToken")
{
$remove[] = $i;
$mediaTypes = (count($tokens[$i]->MediaTypes) == 0 ? $defaultMediaTypes : $tokens[$i]->MediaTypes);
foreach ($mediaTypes as $mediaType)
{
if (!isset($variables[$mediaType]))
{
$variables[$mediaType] = array();
}
}
// Read the variable declaration tokens
for($i = $i; $i < $l; $i++)
{
// Found a variable declaration => read the variable values
if (get_class($tokens[$i]) === "CssAtVariablesDeclarationToken")
{
foreach ($mediaTypes as $mediaType)
{
$variables[$mediaType][$tokens[$i]->Property] = $tokens[$i]->Value;
}
$remove[] = $i;
}
// Found the variables end token => break;
elseif (get_class($tokens[$i]) === "CssAtVariablesEndToken")
{
$remove[] = $i;
break;
}
}
}
}
// Variables in @variables at-rule blocks
foreach($variables as $mediaType => $null)
{
foreach($variables[$mediaType] as $variable => $value)
{
// If a var() statement in a variable value found...
if (stripos($value, "var") !== false && preg_match_all("/var\((.+)\)/iSU", $value, $m))
{
// ... then replace the var() statement with the variable values.
for ($i = 0, $l = count($m[0]); $i < $l; $i++)
{
$variables[$mediaType][$variable] = str_replace($m[0][$i], (isset($variables[$mediaType][$m[1][$i]]) ? $variables[$mediaType][$m[1][$i]] : ""), $variables[$mediaType][$variable]);
}
}
}
}
// Remove the complete @variables at-rule block
foreach ($remove as $i)
{
$tokens[$i] = null;
}
if (!($plugin = $this->minifier->getPlugin("CssVariablesMinifierPlugin")))
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The plugin <code>CssVariablesMinifierPlugin</code> was not found but is required for <code>" . __CLASS__ . "</code>"));
}
else
{
$plugin->setVariables($variables);
}
return count($remove);
}
}
/**
* {@link aCssParserPlugin Parser plugin} for preserve parsing url() values.
*
* This plugin return no {@link aCssToken CssToken} but ensures that url() values will get parsed properly.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssUrlParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("(", ")");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return false;
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of string
if ($char === "(" && strtolower(substr($this->parser->getSource(), $index - 3, 4)) === "url(" && $state !== "T_URL")
{
$this->parser->pushState("T_URL");
$this->parser->setExclusive(__CLASS__);
}
// Escaped LF in url => remove escape backslash and LF
elseif ($char === "\n" && $previousChar === "\\" && $state === "T_URL")
{
$this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -2));
}
// Parse error: Unescaped LF in string literal
elseif ($char === "\n" && $previousChar !== "\\" && $state === "T_URL")
{
$line = $this->parser->getBuffer();
$this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -1) . ")"); // Replace the LF with the url string delimiter
$this->parser->popState();
$this->parser->unsetExclusive();
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated string literal", $line . "_"));
}
// End of string
elseif ($char === ")" && $state === "T_URL")
{
$this->parser->popState();
$this->parser->unsetExclusive();
}
else
{
return false;
}
return true;
}
}
/**
* {@link aCssParserPlugin Parser plugin} for preserve parsing string values.
*
* This plugin return no {@link aCssToken CssToken} but ensures that string values will get parsed properly.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssStringParserPlugin extends aCssParserPlugin
{
/**
* Current string delimiter char.
*
* @var string
*/
private $delimiterChar = null;
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("\"", "'", "\n");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return false;
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of string
if (($char === "\"" || $char === "'") && $state !== "T_STRING")
{
$this->delimiterChar = $char;
$this->parser->pushState("T_STRING");
$this->parser->setExclusive(__CLASS__);
}
// Escaped LF in string => remove escape backslash and LF
elseif ($char === "\n" && $previousChar === "\\" && $state === "T_STRING")
{
$this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -2));
}
// Parse error: Unescaped LF in string literal
elseif ($char === "\n" && $previousChar !== "\\" && $state === "T_STRING")
{
$line = $this->parser->getBuffer();
$this->parser->popState();
$this->parser->unsetExclusive();
$this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -1) . $this->delimiterChar); // Replace the LF with the current string char
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated string literal", $line . "_"));
$this->delimiterChar = null;
}
// End of string
elseif ($char === $this->delimiterChar && $state === "T_STRING")
{
// If the Previous char is a escape char count the amount of the previous escape chars. If the amount of
// escape chars is uneven do not end the string
if ($previousChar == "\\")
{
$source = $this->parser->getSource();
$c = 1;
$i = $index - 2;
while (substr($source, $i, 1) === "\\")
{
$c++; $i--;
}
if ($c % 2)
{
return false;
}
}
$this->parser->popState();
$this->parser->unsetExclusive();
$this->delimiterChar = null;
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssMinifierFilter minifier filter} sorts the ruleset declarations of a ruleset by name.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Rowan Beentje <http://assanka.net>
* @copyright Rowan Beentje <http://assanka.net>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssSortRulesetPropertiesMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value larger than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$r = 0;
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
// Only look for ruleset start rules
if (get_class($tokens[$i]) !== "CssRulesetStartToken") { continue; }
// Look for the corresponding ruleset end
$endIndex = false;
for ($ii = $i + 1; $ii < $l; $ii++)
{
if (get_class($tokens[$ii]) !== "CssRulesetEndToken") { continue; }
$endIndex = $ii;
break;
}
if (!$endIndex) { break; }
$startIndex = $i;
$i = $endIndex;
// Skip if there's only one token in this ruleset
if ($endIndex - $startIndex <= 2) { continue; }
// Ensure that everything between the start and end is a declaration token, for safety
for ($ii = $startIndex + 1; $ii < $endIndex; $ii++)
{
if (get_class($tokens[$ii]) !== "CssRulesetDeclarationToken") { continue(2); }
}
$declarations = array_slice($tokens, $startIndex + 1, $endIndex - $startIndex - 1);
// Check whether a sort is required
$sortRequired = $lastPropertyName = false;
foreach ($declarations as $declaration)
{
if ($lastPropertyName)
{
if (strcmp($lastPropertyName, $declaration->Property) > 0)
{
$sortRequired = true;
break;
}
}
$lastPropertyName = $declaration->Property;
}
if (!$sortRequired) { continue; }
// Arrange the declarations alphabetically by name
usort($declarations, array(__CLASS__, "userDefinedSort1"));
// Update "IsLast" property
for ($ii = 0, $ll = count($declarations) - 1; $ii <= $ll; $ii++)
{
if ($ii == $ll)
{
$declarations[$ii]->IsLast = true;
}
else
{
$declarations[$ii]->IsLast = false;
}
}
// Splice back into the array.
array_splice($tokens, $startIndex + 1, $endIndex - $startIndex - 1, $declarations);
$r += $endIndex - $startIndex - 1;
}
return $r;
}
/**
* User defined sort function.
*
* @return integer
*/
public static function userDefinedSort1($a, $b)
{
return strcmp($a->Property, $b->Property);
}
}
/**
* This {@link aCssToken CSS token} represents the start of a ruleset.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRulesetStartToken extends aCssRulesetStartToken
{
/**
* Array of selectors.
*
* @var array
*/
public $Selectors = array();
/**
* Set the properties of a ruleset token.
*
* @param array $selectors Selectors of the ruleset
* @return void
*/
public function __construct(array $selectors = array())
{
$this->Selectors = $selectors;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return implode(",", $this->Selectors) . "{";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing ruleset block with including declarations.
*
* Found rulesets will add a {@link CssRulesetStartToken} and {@link CssRulesetEndToken} to the
* parser; including declarations as {@link CssRulesetDeclarationToken}.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRulesetParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array(",", "{", "}", ":", ";");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_MEDIA", "T_RULESET::SELECTORS", "T_RULESET", "T_RULESET_DECLARATION");
}
/**
* Selectors.
*
* @var array
*/
private $selectors = array();
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of Ruleset and selectors
if ($char === "," && ($state === "T_DOCUMENT" || $state === "T_AT_MEDIA" || $state === "T_RULESET::SELECTORS"))
{
if ($state !== "T_RULESET::SELECTORS")
{
$this->parser->pushState("T_RULESET::SELECTORS");
}
$this->selectors[] = $this->parser->getAndClearBuffer(",{");
}
// End of selectors and start of declarations
elseif ($char === "{" && ($state === "T_DOCUMENT" || $state === "T_AT_MEDIA" || $state === "T_RULESET::SELECTORS"))
{
if ($this->parser->getBuffer() !== "")
{
$this->selectors[] = $this->parser->getAndClearBuffer(",{");
if ($state == "T_RULESET::SELECTORS")
{
$this->parser->popState();
}
$this->parser->pushState("T_RULESET");
$this->parser->appendToken(new CssRulesetStartToken($this->selectors));
$this->selectors = array();
}
}
// Start of declaration
elseif ($char === ":" && $state === "T_RULESET")
{
$this->parser->pushState("T_RULESET_DECLARATION");
$this->buffer = $this->parser->getAndClearBuffer(":;", true);
}
// Unterminated ruleset declaration
elseif ($char === ":" && $state === "T_RULESET_DECLARATION")
{
// Ignore Internet Explorer filter declarations
if ($this->buffer === "filter")
{
return false;
}
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_"));
}
// End of declaration
elseif (($char === ";" || $char === "}") && $state === "T_RULESET_DECLARATION")
{
$value = $this->parser->getAndClearBuffer(";}");
if (strtolower(substr($value, -10, 10)) === "!important")
{
$value = trim(substr($value, 0, -10));
$isImportant = true;
}
else
{
$isImportant = false;
}
$this->parser->popState();
$this->parser->appendToken(new CssRulesetDeclarationToken($this->buffer, $value, $this->parser->getMediaTypes(), $isImportant));
// Declaration ends with a right curly brace; so we have to end the ruleset
if ($char === "}")
{
$this->parser->appendToken(new CssRulesetEndToken());
$this->parser->popState();
}
$this->buffer = "";
}
// End of ruleset
elseif ($char === "}" && $state === "T_RULESET")
{
$this->parser->popState();
$this->parser->clearBuffer();
$this->parser->appendToken(new CssRulesetEndToken());
$this->buffer = "";
$this->selectors = array();
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the end of a ruleset.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRulesetEndToken extends aCssRulesetEndToken
{
}
/**
* This {@link aCssToken CSS token} represents a ruleset declaration.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRulesetDeclarationToken extends aCssDeclarationToken
{
/**
* Media types of the declaration.
*
* @var array
*/
public $MediaTypes = array("all");
/**
* Set the properties of a ddocument- or at-rule @media level declaration.
*
* @param string $property Property of the declaration
* @param string $value Value of the declaration
* @param mixed $mediaTypes Media types of the declaration
* @param boolean $isImportant Is the !important flag is set
* @param boolean $isLast Is the declaration the last one of the ruleset
* @return void
*/
public function __construct($property, $value, $mediaTypes = null, $isImportant = false, $isLast = false)
{
parent::__construct($property, $value, $isImportant, $isLast);
$this->MediaTypes = $mediaTypes ? $mediaTypes : array("all");
}
}
/**
* This {@link aCssMinifierFilter minifier filter} sets the IsLast property of any last declaration in a ruleset,
* @font-face at-rule or @page at-rule block. If the property IsLast is TRUE the decrations will get stringified
* without tailing semicolon.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRemoveLastDelarationSemiColonMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
$current = get_class($tokens[$i]);
$next = isset($tokens[$i+1]) ? get_class($tokens[$i+1]) : false;
if (($current === "CssRulesetDeclarationToken" && $next === "CssRulesetEndToken") ||
($current === "CssAtFontFaceDeclarationToken" && $next === "CssAtFontFaceEndToken") ||
($current === "CssAtPageDeclarationToken" && $next === "CssAtPageEndToken"))
{
$tokens[$i]->IsLast = true;
}
}
return 0;
}
}
/**
* This {@link aCssMinifierFilter minifier filter} will remove any empty rulesets (including @keyframes at-rule block
* rulesets).
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRemoveEmptyRulesetsMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$r = 0;
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
$current = get_class($tokens[$i]);
$next = isset($tokens[$i + 1]) ? get_class($tokens[$i + 1]) : false;
if (($current === "CssRulesetStartToken" && $next === "CssRulesetEndToken") ||
($current === "CssAtKeyframesRulesetStartToken" && $next === "CssAtKeyframesRulesetEndToken" && !array_intersect(array("from", "0%", "to", "100%"), array_map("strtolower", $tokens[$i]->Selectors)))
)
{
$tokens[$i] = null;
$tokens[$i + 1] = null;
$i++;
$r = $r + 2;
}
}
return $r;
}
}
/**
* This {@link aCssMinifierFilter minifier filter} will remove any empty @font-face, @keyframes, @media and @page
* at-rule blocks.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRemoveEmptyAtBlocksMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$r = 0;
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
$current = get_class($tokens[$i]);
$next = isset($tokens[$i + 1]) ? get_class($tokens[$i + 1]) : false;
if (($current === "CssAtFontFaceStartToken" && $next === "CssAtFontFaceEndToken") ||
($current === "CssAtKeyframesStartToken" && $next === "CssAtKeyframesEndToken") ||
($current === "CssAtPageStartToken" && $next === "CssAtPageEndToken") ||
($current === "CssAtMediaStartToken" && $next === "CssAtMediaEndToken"))
{
$tokens[$i] = null;
$tokens[$i + 1] = null;
$i++;
$r = $r + 2;
}
}
return $r;
}
}
/**
* This {@link aCssMinifierFilter minifier filter} will remove any comments from the array of parsed tokens.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssRemoveCommentsMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$r = 0;
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
if (get_class($tokens[$i]) === "CssCommentToken")
{
$tokens[$i] = null;
$r++;
}
}
return $r;
}
}
/**
* CSS Parser.
*
* @package CssMin/Parser
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssParser
{
/**
* Parse buffer.
*
* @var string
*/
private $buffer = "";
/**
* {@link aCssParserPlugin Plugins}.
*
* @var array
*/
private $plugins = array();
/**
* Source to parse.
*
* @var string
*/
private $source = "";
/**
* Current state.
*
* @var integer
*/
private $state = "T_DOCUMENT";
/**
* Exclusive state.
*
* @var string
*/
private $stateExclusive = false;
/**
* Media types state.
*
* @var mixed
*/
private $stateMediaTypes = false;
/**
* State stack.
*
* @var array
*/
private $states = array("T_DOCUMENT");
/**
* Parsed tokens.
*
* @var array
*/
private $tokens = array();
/**
* Constructer.
*
* Create instances of the used {@link aCssParserPlugin plugins}.
*
* @param string $source CSS source [optional]
* @param array $plugins Plugin configuration [optional]
* @return void
*/
public function __construct($source = null, array $plugins = null)
{
$plugins = array_merge(array
(
"Comment" => true,
"String" => true,
"Url" => true,
"Expression" => true,
"Ruleset" => true,
"AtCharset" => true,
"AtFontFace" => true,
"AtImport" => true,
"AtKeyframes" => true,
"AtMedia" => true,
"AtPage" => true,
"AtVariables" => true
), is_array($plugins) ? $plugins : array());
// Create plugin instances
foreach ($plugins as $name => $config)
{
if ($config !== false)
{
$class = "Css" . $name . "ParserPlugin";
$config = is_array($config) ? $config : array();
if (class_exists($class))
{
$this->plugins[] = new $class($this, $config);
}
else
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The plugin <code>" . $name . "</code> with the class name <code>" . $class . "</code> was not found"));
}
}
}
if (!is_null($source))
{
$this->parse($source);
}
}
/**
* Append a token to the array of tokens.
*
* @param aCssToken $token Token to append
* @return void
*/
public function appendToken(aCssToken $token)
{
$this->tokens[] = $token;
}
/**
* Clears the current buffer.
*
* @return void
*/
public function clearBuffer()
{
$this->buffer = "";
}
/**
* Returns and clear the current buffer.
*
* @param string $trim Chars to use to trim the returned buffer
* @param boolean $tolower if TRUE the returned buffer will get converted to lower case
* @return string
*/
public function getAndClearBuffer($trim = "", $tolower = false)
{
$r = $this->getBuffer($trim, $tolower);
$this->buffer = "";
return $r;
}
/**
* Returns the current buffer.
*
* @param string $trim Chars to use to trim the returned buffer
* @param boolean $tolower if TRUE the returned buffer will get converted to lower case
* @return string
*/
public function getBuffer($trim = "", $tolower = false)
{
$r = $this->buffer;
if ($trim)
{
$r = trim($r, " \t\n\r\0\x0B" . $trim);
}
if ($tolower)
{
$r = strtolower($r);
}
return $r;
}
/**
* Returns the current media types state.
*
* @return array
*/
public function getMediaTypes()
{
return $this->stateMediaTypes;
}
/**
* Returns the CSS source.
*
* @return string
*/
public function getSource()
{
return $this->source;
}
/**
* Returns the current state.
*
* @return integer The current state
*/
public function getState()
{
return $this->state;
}
/**
* Returns a plugin by class name.
*
* @param string $name Class name of the plugin
* @return aCssParserPlugin
*/
public function getPlugin($class)
{
static $index = null;
if (is_null($index))
{
$index = array();
for ($i = 0, $l = count($this->plugins); $i < $l; $i++)
{
$index[get_class($this->plugins[$i])] = $i;
}
}
return isset($index[$class]) ? $this->plugins[$index[$class]] : false;
}
/**
* Returns the parsed tokens.
*
* @return array
*/
public function getTokens()
{
return $this->tokens;
}
/**
* Returns if the current state equals the passed state.
*
* @param integer $state State to compare with the current state
* @return boolean TRUE is the state equals to the passed state; FALSE if not
*/
public function isState($state)
{
return ($this->state == $state);
}
/**
* Parse the CSS source and return a array with parsed tokens.
*
* @param string $source CSS source
* @return array Array with tokens
*/
public function parse($source)
{
// Reset
$this->source = "";
$this->tokens = array();
// Create a global and plugin lookup table for trigger chars; set array of plugins as local variable and create
// several helper variables for plugin handling
$globalTriggerChars = "";
$plugins = $this->plugins;
$pluginCount = count($plugins);
$pluginIndex = array();
$pluginTriggerStates = array();
$pluginTriggerChars = array();
for ($i = 0, $l = count($plugins); $i < $l; $i++)
{
$tPluginClassName = get_class($plugins[$i]);
$pluginTriggerChars[$i] = implode("", $plugins[$i]->getTriggerChars());
$tPluginTriggerStates = $plugins[$i]->getTriggerStates();
$pluginTriggerStates[$i] = $tPluginTriggerStates === false ? false : "|" . implode("|", $tPluginTriggerStates) . "|";
$pluginIndex[$tPluginClassName] = $i;
for ($ii = 0, $ll = strlen($pluginTriggerChars[$i]); $ii < $ll; $ii++)
{
$c = substr($pluginTriggerChars[$i], $ii, 1);
if (strpos($globalTriggerChars, $c) === false)
{
$globalTriggerChars .= $c;
}
}
}
// Normalise line endings
$source = str_replace("\r\n", "\n", $source); // Windows to Unix line endings
$source = str_replace("\r", "\n", $source); // Mac to Unix line endings
$this->source = $source;
// Variables
$buffer = &$this->buffer;
$exclusive = &$this->stateExclusive;
$state = &$this->state;
$c = $p = null;
// --
for ($i = 0, $l = strlen($source); $i < $l; $i++)
{
// Set the current Char
$c = $source[$i]; // Is faster than: $c = substr($source, $i, 1);
// Normalize and filter double whitespace characters
if ($exclusive === false)
{
if ($c === "\n" || $c === "\t")
{
$c = " ";
}
if ($c === " " && $p === " ")
{
continue;
}
}
$buffer .= $c;
// Extended processing only if the current char is a global trigger char
if (strpos($globalTriggerChars, $c) !== false)
{
// Exclusive state is set; process with the exclusive plugin
if ($exclusive)
{
$tPluginIndex = $pluginIndex[$exclusive];
if (strpos($pluginTriggerChars[$tPluginIndex], $c) !== false && ($pluginTriggerStates[$tPluginIndex] === false || strpos($pluginTriggerStates[$tPluginIndex], $state) !== false))
{
$r = $plugins[$tPluginIndex]->parse($i, $c, $p, $state);
// Return value is TRUE => continue with next char
if ($r === true)
{
continue;
}
// Return value is numeric => set new index and continue with next char
elseif ($r !== false && $r != $i)
{
$i = $r;
continue;
}
}
}
// Else iterate through the plugins
else
{
$triggerState = "|" . $state . "|";
for ($ii = 0, $ll = $pluginCount; $ii < $ll; $ii++)
{
// Only process if the current char is one of the plugin trigger chars
if (strpos($pluginTriggerChars[$ii], $c) !== false && ($pluginTriggerStates[$ii] === false || strpos($pluginTriggerStates[$ii], $triggerState) !== false))
{
// Process with the plugin
$r = $plugins[$ii]->parse($i, $c, $p, $state);
// Return value is TRUE => break the plugin loop and and continue with next char
if ($r === true)
{
break;
}
// Return value is numeric => set new index, break the plugin loop and and continue with next char
elseif ($r !== false && $r != $i)
{
$i = $r;
break;
}
}
}
}
}
$p = $c; // Set the parent char
}
return $this->tokens;
}
/**
* Remove the last state of the state stack and return the removed stack value.
*
* @return integer Removed state value
*/
public function popState()
{
$r = array_pop($this->states);
$this->state = $this->states[count($this->states) - 1];
return $r;
}
/**
* Adds a new state onto the state stack.
*
* @param integer $state State to add onto the state stack.
* @return integer The index of the added state in the state stacks
*/
public function pushState($state)
{
$r = array_push($this->states, $state);
$this->state = $this->states[count($this->states) - 1];
return $r;
}
/**
* Sets/restores the buffer.
*
* @param string $buffer Buffer to set
* @return void
*/
public function setBuffer($buffer)
{
$this->buffer = $buffer;
}
/**
* Set the exclusive state.
*
* @param string $exclusive Exclusive state
* @return void
*/
public function setExclusive($exclusive)
{
$this->stateExclusive = $exclusive;
}
/**
* Set the media types state.
*
* @param array $mediaTypes Media types state
* @return void
*/
public function setMediaTypes(array $mediaTypes)
{
$this->stateMediaTypes = $mediaTypes;
}
/**
* Sets the current state in the state stack; equals to {@link CssParser::popState()} + {@link CssParser::pushState()}.
*
* @param integer $state State to set
* @return integer
*/
public function setState($state)
{
$r = array_pop($this->states);
array_push($this->states, $state);
$this->state = $this->states[count($this->states) - 1];
return $r;
}
/**
* Removes the exclusive state.
*
* @return void
*/
public function unsetExclusive()
{
$this->stateExclusive = false;
}
/**
* Removes the media types state.
*
* @return void
*/
public function unsetMediaTypes()
{
$this->stateMediaTypes = false;
}
}
/**
* {@link aCssFromatter Formatter} returning the CSS source in {@link http://goo.gl/j4XdU OTBS indent style} (The One True Brace Style).
*
* @package CssMin/Formatter
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssOtbsFormatter extends aCssFormatter
{
/**
* Implements {@link aCssFormatter::__toString()}.
*
* @return string
*/
public function __toString()
{
$r = array();
$level = 0;
for ($i = 0, $l = count($this->tokens); $i < $l; $i++)
{
$token = $this->tokens[$i];
$class = get_class($token);
$indent = str_repeat($this->indent, $level);
if ($class === "CssCommentToken")
{
$lines = array_map("trim", explode("\n", $token->Comment));
for ($ii = 0, $ll = count($lines); $ii < $ll; $ii++)
{
$r[] = $indent . (substr($lines[$ii], 0, 1) == "*" ? " " : "") . $lines[$ii];
}
}
elseif ($class === "CssAtCharsetToken")
{
$r[] = $indent . "@charset " . $token->Charset . ";";
}
elseif ($class === "CssAtFontFaceStartToken")
{
$r[] = $indent . "@font-face {";
$level++;
}
elseif ($class === "CssAtImportToken")
{
$r[] = $indent . "@import " . $token->Import . " " . implode(", ", $token->MediaTypes) . ";";
}
elseif ($class === "CssAtKeyframesStartToken")
{
$r[] = $indent . "@keyframes " . $token->Name . " {";
$level++;
}
elseif ($class === "CssAtMediaStartToken")
{
$r[] = $indent . "@media " . implode(", ", $token->MediaTypes) . " {";
$level++;
}
elseif ($class === "CssAtPageStartToken")
{
$r[] = $indent . "@page {";
$level++;
}
elseif ($class === "CssAtVariablesStartToken")
{
$r[] = $indent . "@variables " . implode(", ", $token->MediaTypes) . " {";
$level++;
}
elseif ($class === "CssRulesetStartToken" || $class === "CssAtKeyframesRulesetStartToken")
{
$r[] = $indent . implode(", ", $token->Selectors) . " {";
$level++;
}
elseif ($class === "CssAtFontFaceDeclarationToken"
|| $class === "CssAtKeyframesRulesetDeclarationToken"
|| $class === "CssAtPageDeclarationToken"
|| $class === "CssAtVariablesDeclarationToken"
|| $class === "CssRulesetDeclarationToken"
)
{
$declaration = $indent . $token->Property . ": ";
if ($this->padding)
{
$declaration = str_pad($declaration, $this->padding, " ", STR_PAD_RIGHT);
}
$r[] = $declaration . $token->Value . ($token->IsImportant ? " !important" : "") . ";";
}
elseif ($class === "CssAtFontFaceEndToken"
|| $class === "CssAtMediaEndToken"
|| $class === "CssAtKeyframesEndToken"
|| $class === "CssAtKeyframesRulesetEndToken"
|| $class === "CssAtPageEndToken"
|| $class === "CssAtVariablesEndToken"
|| $class === "CssRulesetEndToken"
)
{
$level--;
$r[] = str_repeat($indent, $level) . "}";
}
}
return implode("\n", $r);
}
}
/**
* This {@link aCssToken CSS token} is a utility token that extends {@link aNullToken} and returns only a empty string.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssNullToken extends aCssToken
{
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "";
}
}
/**
* CSS Minifier.
*
* @package CssMin/Minifier
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssMinifier
{
/**
* {@link aCssMinifierFilter Filters}.
*
* @var array
*/
private $filters = array();
/**
* {@link aCssMinifierPlugin Plugins}.
*
* @var array
*/
private $plugins = array();
/**
* Minified source.
*
* @var string
*/
private $minified = "";
/**
* Constructer.
*
* Creates instances of {@link aCssMinifierFilter filters} and {@link aCssMinifierPlugin plugins}.
*
* @param string $source CSS source [optional]
* @param array $filters Filter configuration [optional]
* @param array $plugins Plugin configuration [optional]
* @return void
*/
public function __construct($source = null, array $filters = null, array $plugins = null)
{
$filters = array_merge(array
(
"ImportImports" => false,
"RemoveComments" => true,
"RemoveEmptyRulesets" => true,
"RemoveEmptyAtBlocks" => true,
"ConvertLevel3Properties" => false,
"ConvertLevel3AtKeyframes" => false,
"Variables" => true,
"RemoveLastDelarationSemiColon" => true
), is_array($filters) ? $filters : array());
$plugins = array_merge(array
(
"Variables" => true,
"ConvertFontWeight" => false,
"ConvertHslColors" => false,
"ConvertRgbColors" => false,
"ConvertNamedColors" => false,
"CompressColorValues" => false,
"CompressUnitValues" => false,
"CompressExpressionValues" => false
), is_array($plugins) ? $plugins : array());
// Filters
foreach ($filters as $name => $config)
{
if ($config !== false)
{
$class = "Css" . $name . "MinifierFilter";
$config = is_array($config) ? $config : array();
if (class_exists($class))
{
$this->filters[] = new $class($this, $config);
}
else
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The filter <code>" . $name . "</code> with the class name <code>" . $class . "</code> was not found"));
}
}
}
// Plugins
foreach ($plugins as $name => $config)
{
if ($config !== false)
{
$class = "Css" . $name . "MinifierPlugin";
$config = is_array($config) ? $config : array();
if (class_exists($class))
{
$this->plugins[] = new $class($this, $config);
}
else
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The plugin <code>" . $name . "</code> with the class name <code>" . $class . "</code> was not found"));
}
}
}
// --
if (!is_null($source))
{
$this->minify($source);
}
}
/**
* Returns the minified Source.
*
* @return string
*/
public function getMinified()
{
return $this->minified;
}
/**
* Returns a plugin by class name.
*
* @param string $name Class name of the plugin
* @return aCssMinifierPlugin
*/
public function getPlugin($class)
{
static $index = null;
if (is_null($index))
{
$index = array();
for ($i = 0, $l = count($this->plugins); $i < $l; $i++)
{
$index[get_class($this->plugins[$i])] = $i;
}
}
return isset($index[$class]) ? $this->plugins[$index[$class]] : false;
}
/**
* Minifies the CSS source.
*
* @param string $source CSS source
* @return string
*/
public function minify($source)
{
// Variables
$r = "";
$parser = new CssParser($source);
$tokens = $parser->getTokens();
$filters = $this->filters;
$filterCount = count($this->filters);
$plugins = $this->plugins;
$pluginCount = count($plugins);
$pluginIndex = array();
$pluginTriggerTokens = array();
$globalTriggerTokens = array();
for ($i = 0, $l = count($plugins); $i < $l; $i++)
{
$tPluginClassName = get_class($plugins[$i]);
$pluginTriggerTokens[$i] = $plugins[$i]->getTriggerTokens();
foreach ($pluginTriggerTokens[$i] as $v)
{
if (!in_array($v, $globalTriggerTokens))
{
$globalTriggerTokens[] = $v;
}
}
$pluginTriggerTokens[$i] = "|" . implode("|", $pluginTriggerTokens[$i]) . "|";
$pluginIndex[$tPluginClassName] = $i;
}
$globalTriggerTokens = "|" . implode("|", $globalTriggerTokens) . "|";
/*
* Apply filters
*/
for($i = 0; $i < $filterCount; $i++)
{
// Apply the filter; if the return value is larger than 0...
if ($filters[$i]->apply($tokens) > 0)
{
// ...then filter null values and rebuild the token array
$tokens = array_values(array_filter($tokens));
}
}
$tokenCount = count($tokens);
/*
* Apply plugins
*/
for($i = 0; $i < $tokenCount; $i++)
{
$triggerToken = "|" . get_class($tokens[$i]) . "|";
if (strpos($globalTriggerTokens, $triggerToken) !== false)
{
for($ii = 0; $ii < $pluginCount; $ii++)
{
if (strpos($pluginTriggerTokens[$ii], $triggerToken) !== false || $pluginTriggerTokens[$ii] === false)
{
// Apply the plugin; if the return value is TRUE continue to the next token
if ($plugins[$ii]->apply($tokens[$i]) === true)
{
continue 2;
}
}
}
}
}
// Stringify the tokens
for($i = 0; $i < $tokenCount; $i++)
{
$r .= (string) $tokens[$i];
}
$this->minified = $r;
return $r;
}
}
/**
* CssMin - A (simple) css minifier with benefits
*
* --
* Copyright (c) 2011 Joe Scylla <joe.scylla@gmail.com>
*
* 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:
*
* 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,
* 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.
* --
*
* @package CssMin
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssMin
{
/**
* Index of classes
*
* @var array
*/
private static $classIndex = array();
/**
* Parse/minify errors
*
* @var array
*/
private static $errors = array();
/**
* Verbose output.
*
* @var boolean
*/
private static $isVerbose = false;
/**
* {@link http://goo.gl/JrW54 Autoload} function of CssMin.
*
* @param string $class Name of the class
* @return void
*/
public static function autoload($class)
{
if (isset(self::$classIndex[$class]))
{
require(self::$classIndex[$class]);
}
}
/**
* Return errors
*
* @return array of {CssError}.
*/
public static function getErrors()
{
return self::$errors;
}
/**
* Returns if there were errors.
*
* @return boolean
*/
public static function hasErrors()
{
return count(self::$errors) > 0;
}
/**
* Initialises CssMin.
*
* @return void
*/
public static function initialise()
{
// Create the class index for autoloading or including
$paths = array(dirname(__FILE__));
while (list($i, $path) = each($paths))
{
$subDirectorys = glob($path . "*", GLOB_MARK | GLOB_ONLYDIR | GLOB_NOSORT);
if (is_array($subDirectorys))
{
foreach ($subDirectorys as $subDirectory)
{
$paths[] = $subDirectory;
}
}
$files = glob($path . "*.php", 0);
if (is_array($files))
{
foreach ($files as $file)
{
$class = substr(basename($file), 0, -4);
self::$classIndex[$class] = $file;
}
}
}
krsort(self::$classIndex);
// Only use autoloading if spl_autoload_register() is available and no __autoload() is defined (because
// __autoload() breaks if spl_autoload_register() is used.
if (function_exists("spl_autoload_register") && !is_callable("__autoload"))
{
spl_autoload_register(array(__CLASS__, "autoload"));
}
// Otherwise include all class files
else
{
foreach (self::$classIndex as $class => $file)
{
if (!class_exists($class))
{
require_once($file);
}
}
}
}
/**
* Minifies CSS source.
*
* @param string $source CSS source
* @param array $filters Filter configuration [optional]
* @param array $plugins Plugin configuration [optional]
* @return string Minified CSS
*/
public static function minify($source, array $filters = null, array $plugins = null)
{
self::$errors = array();
$minifier = new CssMinifier($source, $filters, $plugins);
return $minifier->getMinified();
}
/**
* Parse the CSS source.
*
* @param string $source CSS source
* @param array $plugins Plugin configuration [optional]
* @return array Array of aCssToken
*/
public static function parse($source, array $plugins = null)
{
self::$errors = array();
$parser = new CssParser($source, $plugins);
return $parser->getTokens();
}
/**
* --
*
* @param boolean $to
* @return boolean
*/
public static function setVerbose($to)
{
self::$isVerbose = (boolean) $to;
return self::$isVerbose;
}
/**
* --
*
* @param CssError $error
* @return void
*/
public static function triggerError(CssError $error)
{
self::$errors[] = $error;
if (self::$isVerbose)
{
trigger_error((string) $error, E_USER_WARNING);
}
}
}
// Initialises CssMin
CssMin::initialise();
/**
* This {@link aCssMinifierFilter minifier filter} import external css files defined with the @import at-rule into the
* current stylesheet.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssImportImportsMinifierFilter extends aCssMinifierFilter
{
/**
* Array with already imported external stylesheets.
*
* @var array
*/
private $imported = array();
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
if (!isset($this->configuration["BasePath"]) || !is_dir($this->configuration["BasePath"]))
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Base path <code>" . ($this->configuration["BasePath"] ? $this->configuration["BasePath"] : "null"). "</code> is not a directory"));
return 0;
}
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
if (get_class($tokens[$i]) === "CssAtImportToken")
{
$import = $this->configuration["BasePath"] . "/" . $tokens[$i]->Import;
// Import file was not found/is not a file
if (!is_file($import))
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Import file <code>" . $import. "</code> was not found.", (string) $tokens[$i]));
}
// Import file already imported; remove this @import at-rule to prevent recursions
elseif (in_array($import, $this->imported))
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Import file <code>" . $import. "</code> was already imported.", (string) $tokens[$i]));
$tokens[$i] = null;
}
else
{
$this->imported[] = $import;
$parser = new CssParser(file_get_contents($import));
$import = $parser->getTokens();
// The @import at-rule has media types defined requiring special handling
if (count($tokens[$i]->MediaTypes) > 0 && !(count($tokens[$i]->MediaTypes) == 1 && $tokens[$i]->MediaTypes[0] == "all"))
{
$blocks = array();
/*
* Filter or set media types of @import at-rule or remove the @import at-rule if no media type is matching the parent @import at-rule
*/
for($ii = 0, $ll = count($import); $ii < $ll; $ii++)
{
if (get_class($import[$ii]) === "CssAtImportToken")
{
// @import at-rule defines no media type or only the "all" media type; set the media types to the one defined in the parent @import at-rule
if (count($import[$ii]->MediaTypes) == 0 || (count($import[$ii]->MediaTypes) == 1 && $import[$ii]->MediaTypes[0] == "all"))
{
$import[$ii]->MediaTypes = $tokens[$i]->MediaTypes;
}
// @import at-rule defineds one or more media types; filter out media types not matching with the parent @import at-rule
elseif (count($import[$ii]->MediaTypes > 0))
{
foreach ($import[$ii]->MediaTypes as $index => $mediaType)
{
if (!in_array($mediaType, $tokens[$i]->MediaTypes))
{
unset($import[$ii]->MediaTypes[$index]);
}
}
$import[$ii]->MediaTypes = array_values($import[$ii]->MediaTypes);
// If there are no media types left in the @import at-rule remove the @import at-rule
if (count($import[$ii]->MediaTypes) == 0)
{
$import[$ii] = null;
}
}
}
}
/*
* Remove media types of @media at-rule block not defined in the @import at-rule
*/
for($ii = 0, $ll = count($import); $ii < $ll; $ii++)
{
if (get_class($import[$ii]) === "CssAtMediaStartToken")
{
foreach ($import[$ii]->MediaTypes as $index => $mediaType)
{
if (!in_array($mediaType, $tokens[$i]->MediaTypes))
{
unset($import[$ii]->MediaTypes[$index]);
}
$import[$ii]->MediaTypes = array_values($import[$ii]->MediaTypes);
}
}
}
/*
* If no media types left of the @media at-rule block remove the complete block
*/
for($ii = 0, $ll = count($import); $ii < $ll; $ii++)
{
if (get_class($import[$ii]) === "CssAtMediaStartToken")
{
if (count($import[$ii]->MediaTypes) === 0)
{
for ($iii = $ii; $iii < $ll; $iii++)
{
if (get_class($import[$iii]) === "CssAtMediaEndToken")
{
break;
}
}
if (get_class($import[$iii]) === "CssAtMediaEndToken")
{
array_splice($import, $ii, $iii - $ii + 1, array());
$ll = count($import);
}
}
}
}
/*
* If the media types of the @media at-rule equals the media types defined in the @import
* at-rule remove the CssAtMediaStartToken and CssAtMediaEndToken token
*/
for($ii = 0, $ll = count($import); $ii < $ll; $ii++)
{
if (get_class($import[$ii]) === "CssAtMediaStartToken" && count(array_diff($tokens[$i]->MediaTypes, $import[$ii]->MediaTypes)) === 0)
{
for ($iii = $ii; $iii < $ll; $iii++)
{
if (get_class($import[$iii]) == "CssAtMediaEndToken")
{
break;
}
}
if (get_class($import[$iii]) == "CssAtMediaEndToken")
{
unset($import[$ii]);
unset($import[$iii]);
$import = array_values($import);
$ll = count($import);
}
}
}
/**
* Extract CssAtImportToken and CssAtCharsetToken tokens
*/
for($ii = 0, $ll = count($import); $ii < $ll; $ii++)
{
$class = get_class($import[$ii]);
if ($class === "CssAtImportToken" || $class === "CssAtCharsetToken")
{
$blocks = array_merge($blocks, array_splice($import, $ii, 1, array()));
$ll = count($import);
}
}
/*
* Extract the @font-face, @media and @page at-rule block
*/
for($ii = 0, $ll = count($import); $ii < $ll; $ii++)
{
$class = get_class($import[$ii]);
if ($class === "CssAtFontFaceStartToken" || $class === "CssAtMediaStartToken" || $class === "CssAtPageStartToken" || $class === "CssAtVariablesStartToken")
{
for ($iii = $ii; $iii < $ll; $iii++)
{
$class = get_class($import[$iii]);
if ($class === "CssAtFontFaceEndToken" || $class === "CssAtMediaEndToken" || $class === "CssAtPageEndToken" || $class === "CssAtVariablesEndToken")
{
break;
}
}
$class = get_class($import[$iii]);
if (isset($import[$iii]) && ($class === "CssAtFontFaceEndToken" || $class === "CssAtMediaEndToken" || $class === "CssAtPageEndToken" || $class === "CssAtVariablesEndToken"))
{
$blocks = array_merge($blocks, array_splice($import, $ii, $iii - $ii + 1, array()));
$ll = count($import);
}
}
}
// Create the import array with extracted tokens and the rulesets wrapped into a @media at-rule block
$import = array_merge($blocks, array(new CssAtMediaStartToken($tokens[$i]->MediaTypes)), $import, array(new CssAtMediaEndToken()));
}
// Insert the imported tokens
array_splice($tokens, $i, 1, $import);
// Modify parameters of the for-loop
$i--;
$l = count($tokens);
}
}
}
}
}
/**
* {@link aCssParserPlugin Parser plugin} for preserve parsing expression() declaration values.
*
* This plugin return no {@link aCssToken CssToken} but ensures that expression() declaration values will get parsed
* properly.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssExpressionParserPlugin extends aCssParserPlugin
{
/**
* Count of left braces.
*
* @var integer
*/
private $leftBraces = 0;
/**
* Count of right braces.
*
* @var integer
*/
private $rightBraces = 0;
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("(", ")", ";", "}");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return false;
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of expression
if ($char === "(" && strtolower(substr($this->parser->getSource(), $index - 10, 11)) === "expression(" && $state !== "T_EXPRESSION")
{
$this->parser->pushState("T_EXPRESSION");
$this->leftBraces++;
}
// Count left braces
elseif ($char === "(" && $state === "T_EXPRESSION")
{
$this->leftBraces++;
}
// Count right braces
elseif ($char === ")" && $state === "T_EXPRESSION")
{
$this->rightBraces++;
}
// Possible end of expression; if left and right braces are equal the expressen ends
elseif (($char === ";" || $char === "}") && $state === "T_EXPRESSION" && $this->leftBraces === $this->rightBraces)
{
$this->leftBraces = $this->rightBraces = 0;
$this->parser->popState();
return $index - 1;
}
else
{
return false;
}
return true;
}
}
/**
* CSS Error.
*
* @package CssMin
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssError
{
/**
* File.
*
* @var string
*/
public $File = "";
/**
* Line.
*
* @var integer
*/
public $Line = 0;
/**
* Error message.
*
* @var string
*/
public $Message = "";
/**
* Source.
*
* @var string
*/
public $Source = "";
/**
* Constructor triggering the error.
*
* @param string $message Error message
* @param string $source Corresponding line [optional]
* @return void
*/
public function __construct($file, $line, $message, $source = "")
{
$this->File = $file;
$this->Line = $line;
$this->Message = $message;
$this->Source = $source;
}
/**
* Returns the error as formatted string.
*
* @return string
*/
public function __toString()
{
return $this->Message . ($this->Source ? ": <br /><code>" . $this->Source . "</code>": "") . "<br />in file " . $this->File . " at line " . $this->Line;
}
}
/**
* This {@link aCssMinifierPlugin} will convert a color value in rgb notation to hexadecimal notation.
*
* Example:
* <code>
* color: rgb(200,60%,5);
* </code>
*
* Will get converted to:
* <code>
* color:#c89905;
* </code>
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssConvertRgbColorsMinifierPlugin extends aCssMinifierPlugin
{
/**
* Regular expression matching the value.
*
* @var string
*/
private $reMatch = "/rgb\s*\(\s*([0-9%]+)\s*,\s*([0-9%]+)\s*,\s*([0-9%]+)\s*\)/iS";
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (stripos($token->Value, "rgb") !== false && preg_match($this->reMatch, $token->Value, $m))
{
for ($i = 1, $l = count($m); $i < $l; $i++)
{
if (strpos("%", $m[$i]) !== false)
{
$m[$i] = substr($m[$i], 0, -1);
$m[$i] = (int) (256 * ($m[$i] / 100));
}
$m[$i] = str_pad(dechex($m[$i]), 2, "0", STR_PAD_LEFT);
}
$token->Value = str_replace($m[0], "#" . $m[1] . $m[2] . $m[3], $token->Value);
}
return false;
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
}
/**
* This {@link aCssMinifierPlugin} will convert named color values to hexadecimal notation.
*
* Example:
* <code>
* color: black;
* border: 1px solid indigo;
* </code>
*
* Will get converted to:
* <code>
* color:#000;
* border:1px solid #4b0082;
* </code>
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssConvertNamedColorsMinifierPlugin extends aCssMinifierPlugin
{
/**
* Regular expression matching the value.
*
* @var string
*/
private $reMatch = null;
/**
* Transformation table used by the {@link CssConvertNamedColorsMinifierPlugin::reReplace() replacement method}.
*
* @var array
*/
private $transformation = array
(
"aliceblue" => "#f0f8ff",
"antiquewhite" => "#faebd7",
"aqua" => "#0ff",
"aquamarine" => "#7fffd4",
"azure" => "#f0ffff",
"beige" => "#f5f5dc",
"black" => "#000",
"blue" => "#00f",
"blueviolet" => "#8a2be2",
"brown" => "#a52a2a",
"burlywood" => "#deb887",
"cadetblue" => "#5f9ea0",
"chartreuse" => "#7fff00",
"chocolate" => "#d2691e",
"coral" => "#ff7f50",
"cornflowerblue" => "#6495ed",
"cornsilk" => "#fff8dc",
"crimson" => "#dc143c",
"darkblue" => "#00008b",
"darkcyan" => "#008b8b",
"darkgoldenrod" => "#b8860b",
"darkgray" => "#a9a9a9",
"darkgreen" => "#006400",
"darkkhaki" => "#bdb76b",
"darkmagenta" => "#8b008b",
"darkolivegreen" => "#556b2f",
"darkorange" => "#ff8c00",
"darkorchid" => "#9932cc",
"darkred" => "#8b0000",
"darksalmon" => "#e9967a",
"darkseagreen" => "#8fbc8f",
"darkslateblue" => "#483d8b",
"darkslategray" => "#2f4f4f",
"darkturquoise" => "#00ced1",
"darkviolet" => "#9400d3",
"deeppink" => "#ff1493",
"deepskyblue" => "#00bfff",
"dimgray" => "#696969",
"dodgerblue" => "#1e90ff",
"firebrick" => "#b22222",
"floralwhite" => "#fffaf0",
"forestgreen" => "#228b22",
"fuchsia" => "#f0f",
"gainsboro" => "#dcdcdc",
"ghostwhite" => "#f8f8ff",
"gold" => "#ffd700",
"goldenrod" => "#daa520",
"gray" => "#808080",
"green" => "#008000",
"greenyellow" => "#adff2f",
"honeydew" => "#f0fff0",
"hotpink" => "#ff69b4",
"indianred" => "#cd5c5c",
"indigo" => "#4b0082",
"ivory" => "#fffff0",
"khaki" => "#f0e68c",
"lavender" => "#e6e6fa",
"lavenderblush" => "#fff0f5",
"lawngreen" => "#7cfc00",
"lemonchiffon" => "#fffacd",
"lightblue" => "#add8e6",
"lightcoral" => "#f08080",
"lightcyan" => "#e0ffff",
"lightgoldenrodyellow" => "#fafad2",
"lightgreen" => "#90ee90",
"lightgrey" => "#d3d3d3",
"lightpink" => "#ffb6c1",
"lightsalmon" => "#ffa07a",
"lightseagreen" => "#20b2aa",
"lightskyblue" => "#87cefa",
"lightslategray" => "#789",
"lightsteelblue" => "#b0c4de",
"lightyellow" => "#ffffe0",
"lime" => "#0f0",
"limegreen" => "#32cd32",
"linen" => "#faf0e6",
"maroon" => "#800000",
"mediumaquamarine" => "#66cdaa",
"mediumblue" => "#0000cd",
"mediumorchid" => "#ba55d3",
"mediumpurple" => "#9370db",
"mediumseagreen" => "#3cb371",
"mediumslateblue" => "#7b68ee",
"mediumspringgreen" => "#00fa9a",
"mediumturquoise" => "#48d1cc",
"mediumvioletred" => "#c71585",
"midnightblue" => "#191970",
"mintcream" => "#f5fffa",
"mistyrose" => "#ffe4e1",
"moccasin" => "#ffe4b5",
"navajowhite" => "#ffdead",
"navy" => "#000080",
"oldlace" => "#fdf5e6",
"olive" => "#808000",
"olivedrab" => "#6b8e23",
"orange" => "#ffa500",
"orangered" => "#ff4500",
"orchid" => "#da70d6",
"palegoldenrod" => "#eee8aa",
"palegreen" => "#98fb98",
"paleturquoise" => "#afeeee",
"palevioletred" => "#db7093",
"papayawhip" => "#ffefd5",
"peachpuff" => "#ffdab9",
"peru" => "#cd853f",
"pink" => "#ffc0cb",
"plum" => "#dda0dd",
"powderblue" => "#b0e0e6",
"purple" => "#800080",
"red" => "#f00",
"rosybrown" => "#bc8f8f",
"royalblue" => "#4169e1",
"saddlebrown" => "#8b4513",
"salmon" => "#fa8072",
"sandybrown" => "#f4a460",
"seagreen" => "#2e8b57",
"seashell" => "#fff5ee",
"sienna" => "#a0522d",
"silver" => "#c0c0c0",
"skyblue" => "#87ceeb",
"slateblue" => "#6a5acd",
"slategray" => "#708090",
"snow" => "#fffafa",
"springgreen" => "#00ff7f",
"steelblue" => "#4682b4",
"tan" => "#d2b48c",
"teal" => "#008080",
"thistle" => "#d8bfd8",
"tomato" => "#ff6347",
"turquoise" => "#40e0d0",
"violet" => "#ee82ee",
"wheat" => "#f5deb3",
"white" => "#fff",
"whitesmoke" => "#f5f5f5",
"yellow" => "#ff0",
"yellowgreen" => "#9acd32"
);
/**
* Overwrites {@link aCssMinifierPlugin::__construct()}.
*
* The constructor will create the {@link CssConvertNamedColorsMinifierPlugin::$reMatch replace regular expression}
* based on the {@link CssConvertNamedColorsMinifierPlugin::$transformation transformation table}.
*
* @param CssMinifier $minifier The CssMinifier object of this plugin.
* @param array $configuration Plugin configuration [optional]
* @return void
*/
public function __construct(CssMinifier $minifier, array $configuration = array())
{
$this->reMatch = "/(^|\s)+(" . implode("|", array_keys($this->transformation)) . ")(\s|$)+/iS";
parent::__construct($minifier, $configuration);
}
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
$lcValue = strtolower($token->Value);
// Declaration value equals a value in the transformation table => simple replace
if (isset($this->transformation[$lcValue]))
{
$token->Value = $this->transformation[$lcValue];
}
// Declaration value contains a value in the transformation table => regular expression replace
elseif (preg_match($this->reMatch, $token->Value))
{
$token->Value = preg_replace_callback($this->reMatch, array($this, 'reReplace'), $token->Value);
}
return false;
}
/**
* Callback for replacement value.
*
* @param array $match
* @return string
*/
private function reReplace($match)
{
return $match[1] . $this->transformation[strtolower($match[2])] . $match[3];
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
}
/**
* This {@link aCssMinifierFilter minifier filter} triggers on CSS Level 3 properties and will add declaration tokens
* with browser-specific properties.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssConvertLevel3PropertiesMinifierFilter extends aCssMinifierFilter
{
/**
* Css property transformations table. Used to convert CSS3 and proprietary properties to the browser-specific
* counterparts.
*
* @var array
*/
private $transformations = array
(
// Property Array(Mozilla, Webkit, Opera, Internet Explorer); NULL values are placeholders and will get ignored
"animation" => array(null, "-webkit-animation", null, null),
"animation-delay" => array(null, "-webkit-animation-delay", null, null),
"animation-direction" => array(null, "-webkit-animation-direction", null, null),
"animation-duration" => array(null, "-webkit-animation-duration", null, null),
"animation-fill-mode" => array(null, "-webkit-animation-fill-mode", null, null),
"animation-iteration-count" => array(null, "-webkit-animation-iteration-count", null, null),
"animation-name" => array(null, "-webkit-animation-name", null, null),
"animation-play-state" => array(null, "-webkit-animation-play-state", null, null),
"animation-timing-function" => array(null, "-webkit-animation-timing-function", null, null),
"appearance" => array("-moz-appearance", "-webkit-appearance", null, null),
"backface-visibility" => array(null, "-webkit-backface-visibility", null, null),
"background-clip" => array(null, "-webkit-background-clip", null, null),
"background-composite" => array(null, "-webkit-background-composite", null, null),
"background-inline-policy" => array("-moz-background-inline-policy", null, null, null),
"background-origin" => array(null, "-webkit-background-origin", null, null),
"background-position-x" => array(null, null, null, "-ms-background-position-x"),
"background-position-y" => array(null, null, null, "-ms-background-position-y"),
"background-size" => array(null, "-webkit-background-size", null, null),
"behavior" => array(null, null, null, "-ms-behavior"),
"binding" => array("-moz-binding", null, null, null),
"border-after" => array(null, "-webkit-border-after", null, null),
"border-after-color" => array(null, "-webkit-border-after-color", null, null),
"border-after-style" => array(null, "-webkit-border-after-style", null, null),
"border-after-width" => array(null, "-webkit-border-after-width", null, null),
"border-before" => array(null, "-webkit-border-before", null, null),
"border-before-color" => array(null, "-webkit-border-before-color", null, null),
"border-before-style" => array(null, "-webkit-border-before-style", null, null),
"border-before-width" => array(null, "-webkit-border-before-width", null, null),
"border-border-bottom-colors" => array("-moz-border-bottom-colors", null, null, null),
"border-bottom-left-radius" => array("-moz-border-radius-bottomleft", "-webkit-border-bottom-left-radius", null, null),
"border-bottom-right-radius" => array("-moz-border-radius-bottomright", "-webkit-border-bottom-right-radius", null, null),
"border-end" => array("-moz-border-end", "-webkit-border-end", null, null),
"border-end-color" => array("-moz-border-end-color", "-webkit-border-end-color", null, null),
"border-end-style" => array("-moz-border-end-style", "-webkit-border-end-style", null, null),
"border-end-width" => array("-moz-border-end-width", "-webkit-border-end-width", null, null),
"border-fit" => array(null, "-webkit-border-fit", null, null),
"border-horizontal-spacing" => array(null, "-webkit-border-horizontal-spacing", null, null),
"border-image" => array("-moz-border-image", "-webkit-border-image", null, null),
"border-left-colors" => array("-moz-border-left-colors", null, null, null),
"border-radius" => array("-moz-border-radius", "-webkit-border-radius", null, null),
"border-border-right-colors" => array("-moz-border-right-colors", null, null, null),
"border-start" => array("-moz-border-start", "-webkit-border-start", null, null),
"border-start-color" => array("-moz-border-start-color", "-webkit-border-start-color", null, null),
"border-start-style" => array("-moz-border-start-style", "-webkit-border-start-style", null, null),
"border-start-width" => array("-moz-border-start-width", "-webkit-border-start-width", null, null),
"border-top-colors" => array("-moz-border-top-colors", null, null, null),
"border-top-left-radius" => array("-moz-border-radius-topleft", "-webkit-border-top-left-radius", null, null),
"border-top-right-radius" => array("-moz-border-radius-topright", "-webkit-border-top-right-radius", null, null),
"border-vertical-spacing" => array(null, "-webkit-border-vertical-spacing", null, null),
"box-align" => array("-moz-box-align", "-webkit-box-align", null, null),
"box-direction" => array("-moz-box-direction", "-webkit-box-direction", null, null),
"box-flex" => array("-moz-box-flex", "-webkit-box-flex", null, null),
"box-flex-group" => array(null, "-webkit-box-flex-group", null, null),
"box-flex-lines" => array(null, "-webkit-box-flex-lines", null, null),
"box-ordinal-group" => array("-moz-box-ordinal-group", "-webkit-box-ordinal-group", null, null),
"box-orient" => array("-moz-box-orient", "-webkit-box-orient", null, null),
"box-pack" => array("-moz-box-pack", "-webkit-box-pack", null, null),
"box-reflect" => array(null, "-webkit-box-reflect", null, null),
"box-shadow" => array("-moz-box-shadow", "-webkit-box-shadow", null, null),
"box-sizing" => array("-moz-box-sizing", null, null, null),
"color-correction" => array(null, "-webkit-color-correction", null, null),
"column-break-after" => array(null, "-webkit-column-break-after", null, null),
"column-break-before" => array(null, "-webkit-column-break-before", null, null),
"column-break-inside" => array(null, "-webkit-column-break-inside", null, null),
"column-count" => array("-moz-column-count", "-webkit-column-count", null, null),
"column-gap" => array("-moz-column-gap", "-webkit-column-gap", null, null),
"column-rule" => array("-moz-column-rule", "-webkit-column-rule", null, null),
"column-rule-color" => array("-moz-column-rule-color", "-webkit-column-rule-color", null, null),
"column-rule-style" => array("-moz-column-rule-style", "-webkit-column-rule-style", null, null),
"column-rule-width" => array("-moz-column-rule-width", "-webkit-column-rule-width", null, null),
"column-span" => array(null, "-webkit-column-span", null, null),
"column-width" => array("-moz-column-width", "-webkit-column-width", null, null),
"columns" => array(null, "-webkit-columns", null, null),
"filter" => array(__CLASS__, "filter"),
"float-edge" => array("-moz-float-edge", null, null, null),
"font-feature-settings" => array("-moz-font-feature-settings", null, null, null),
"font-language-override" => array("-moz-font-language-override", null, null, null),
"font-size-delta" => array(null, "-webkit-font-size-delta", null, null),
"font-smoothing" => array(null, "-webkit-font-smoothing", null, null),
"force-broken-image-icon" => array("-moz-force-broken-image-icon", null, null, null),
"highlight" => array(null, "-webkit-highlight", null, null),
"hyphenate-character" => array(null, "-webkit-hyphenate-character", null, null),
"hyphenate-locale" => array(null, "-webkit-hyphenate-locale", null, null),
"hyphens" => array(null, "-webkit-hyphens", null, null),
"force-broken-image-icon" => array("-moz-image-region", null, null, null),
"ime-mode" => array(null, null, null, "-ms-ime-mode"),
"interpolation-mode" => array(null, null, null, "-ms-interpolation-mode"),
"layout-flow" => array(null, null, null, "-ms-layout-flow"),
"layout-grid" => array(null, null, null, "-ms-layout-grid"),
"layout-grid-char" => array(null, null, null, "-ms-layout-grid-char"),
"layout-grid-line" => array(null, null, null, "-ms-layout-grid-line"),
"layout-grid-mode" => array(null, null, null, "-ms-layout-grid-mode"),
"layout-grid-type" => array(null, null, null, "-ms-layout-grid-type"),
"line-break" => array(null, "-webkit-line-break", null, "-ms-line-break"),
"line-clamp" => array(null, "-webkit-line-clamp", null, null),
"line-grid-mode" => array(null, null, null, "-ms-line-grid-mode"),
"logical-height" => array(null, "-webkit-logical-height", null, null),
"logical-width" => array(null, "-webkit-logical-width", null, null),
"margin-after" => array(null, "-webkit-margin-after", null, null),
"margin-after-collapse" => array(null, "-webkit-margin-after-collapse", null, null),
"margin-before" => array(null, "-webkit-margin-before", null, null),
"margin-before-collapse" => array(null, "-webkit-margin-before-collapse", null, null),
"margin-bottom-collapse" => array(null, "-webkit-margin-bottom-collapse", null, null),
"margin-collapse" => array(null, "-webkit-margin-collapse", null, null),
"margin-end" => array("-moz-margin-end", "-webkit-margin-end", null, null),
"margin-start" => array("-moz-margin-start", "-webkit-margin-start", null, null),
"margin-top-collapse" => array(null, "-webkit-margin-top-collapse", null, null),
"marquee " => array(null, "-webkit-marquee", null, null),
"marquee-direction" => array(null, "-webkit-marquee-direction", null, null),
"marquee-increment" => array(null, "-webkit-marquee-increment", null, null),
"marquee-repetition" => array(null, "-webkit-marquee-repetition", null, null),
"marquee-speed" => array(null, "-webkit-marquee-speed", null, null),
"marquee-style" => array(null, "-webkit-marquee-style", null, null),
"mask" => array(null, "-webkit-mask", null, null),
"mask-attachment" => array(null, "-webkit-mask-attachment", null, null),
"mask-box-image" => array(null, "-webkit-mask-box-image", null, null),
"mask-clip" => array(null, "-webkit-mask-clip", null, null),
"mask-composite" => array(null, "-webkit-mask-composite", null, null),
"mask-image" => array(null, "-webkit-mask-image", null, null),
"mask-origin" => array(null, "-webkit-mask-origin", null, null),
"mask-position" => array(null, "-webkit-mask-position", null, null),
"mask-position-x" => array(null, "-webkit-mask-position-x", null, null),
"mask-position-y" => array(null, "-webkit-mask-position-y", null, null),
"mask-repeat" => array(null, "-webkit-mask-repeat", null, null),
"mask-repeat-x" => array(null, "-webkit-mask-repeat-x", null, null),
"mask-repeat-y" => array(null, "-webkit-mask-repeat-y", null, null),
"mask-size" => array(null, "-webkit-mask-size", null, null),
"match-nearest-mail-blockquote-color" => array(null, "-webkit-match-nearest-mail-blockquote-color", null, null),
"max-logical-height" => array(null, "-webkit-max-logical-height", null, null),
"max-logical-width" => array(null, "-webkit-max-logical-width", null, null),
"min-logical-height" => array(null, "-webkit-min-logical-height", null, null),
"min-logical-width" => array(null, "-webkit-min-logical-width", null, null),
"object-fit" => array(null, null, "-o-object-fit", null),
"object-position" => array(null, null, "-o-object-position", null),
"opacity" => array(__CLASS__, "opacity"),
"outline-radius" => array("-moz-outline-radius", null, null, null),
"outline-bottom-left-radius" => array("-moz-outline-radius-bottomleft", null, null, null),
"outline-bottom-right-radius" => array("-moz-outline-radius-bottomright", null, null, null),
"outline-top-left-radius" => array("-moz-outline-radius-topleft", null, null, null),
"outline-top-right-radius" => array("-moz-outline-radius-topright", null, null, null),
"padding-after" => array(null, "-webkit-padding-after", null, null),
"padding-before" => array(null, "-webkit-padding-before", null, null),
"padding-end" => array("-moz-padding-end", "-webkit-padding-end", null, null),
"padding-start" => array("-moz-padding-start", "-webkit-padding-start", null, null),
"perspective" => array(null, "-webkit-perspective", null, null),
"perspective-origin" => array(null, "-webkit-perspective-origin", null, null),
"perspective-origin-x" => array(null, "-webkit-perspective-origin-x", null, null),
"perspective-origin-y" => array(null, "-webkit-perspective-origin-y", null, null),
"rtl-ordering" => array(null, "-webkit-rtl-ordering", null, null),
"scrollbar-3dlight-color" => array(null, null, null, "-ms-scrollbar-3dlight-color"),
"scrollbar-arrow-color" => array(null, null, null, "-ms-scrollbar-arrow-color"),
"scrollbar-base-color" => array(null, null, null, "-ms-scrollbar-base-color"),
"scrollbar-darkshadow-color" => array(null, null, null, "-ms-scrollbar-darkshadow-color"),
"scrollbar-face-color" => array(null, null, null, "-ms-scrollbar-face-color"),
"scrollbar-highlight-color" => array(null, null, null, "-ms-scrollbar-highlight-color"),
"scrollbar-shadow-color" => array(null, null, null, "-ms-scrollbar-shadow-color"),
"scrollbar-track-color" => array(null, null, null, "-ms-scrollbar-track-color"),
"stack-sizing" => array("-moz-stack-sizing", null, null, null),
"svg-shadow" => array(null, "-webkit-svg-shadow", null, null),
"tab-size" => array("-moz-tab-size", null, "-o-tab-size", null),
"table-baseline" => array(null, null, "-o-table-baseline", null),
"text-align-last" => array(null, null, null, "-ms-text-align-last"),
"text-autospace" => array(null, null, null, "-ms-text-autospace"),
"text-combine" => array(null, "-webkit-text-combine", null, null),
"text-decorations-in-effect" => array(null, "-webkit-text-decorations-in-effect", null, null),
"text-emphasis" => array(null, "-webkit-text-emphasis", null, null),
"text-emphasis-color" => array(null, "-webkit-text-emphasis-color", null, null),
"text-emphasis-position" => array(null, "-webkit-text-emphasis-position", null, null),
"text-emphasis-style" => array(null, "-webkit-text-emphasis-style", null, null),
"text-fill-color" => array(null, "-webkit-text-fill-color", null, null),
"text-justify" => array(null, null, null, "-ms-text-justify"),
"text-kashida-space" => array(null, null, null, "-ms-text-kashida-space"),
"text-overflow" => array(null, null, "-o-text-overflow", "-ms-text-overflow"),
"text-security" => array(null, "-webkit-text-security", null, null),
"text-size-adjust" => array(null, "-webkit-text-size-adjust", null, "-ms-text-size-adjust"),
"text-stroke" => array(null, "-webkit-text-stroke", null, null),
"text-stroke-color" => array(null, "-webkit-text-stroke-color", null, null),
"text-stroke-width" => array(null, "-webkit-text-stroke-width", null, null),
"text-underline-position" => array(null, null, null, "-ms-text-underline-position"),
"transform" => array("-moz-transform", "-webkit-transform", "-o-transform", null),
"transform-origin" => array("-moz-transform-origin", "-webkit-transform-origin", "-o-transform-origin", null),
"transform-origin-x" => array(null, "-webkit-transform-origin-x", null, null),
"transform-origin-y" => array(null, "-webkit-transform-origin-y", null, null),
"transform-origin-z" => array(null, "-webkit-transform-origin-z", null, null),
"transform-style" => array(null, "-webkit-transform-style", null, null),
"transition" => array("-moz-transition", "-webkit-transition", "-o-transition", null),
"transition-delay" => array("-moz-transition-delay", "-webkit-transition-delay", "-o-transition-delay", null),
"transition-duration" => array("-moz-transition-duration", "-webkit-transition-duration", "-o-transition-duration", null),
"transition-property" => array("-moz-transition-property", "-webkit-transition-property", "-o-transition-property", null),
"transition-timing-function" => array("-moz-transition-timing-function", "-webkit-transition-timing-function", "-o-transition-timing-function", null),
"user-drag" => array(null, "-webkit-user-drag", null, null),
"user-focus" => array("-moz-user-focus", null, null, null),
"user-input" => array("-moz-user-input", null, null, null),
"user-modify" => array("-moz-user-modify", "-webkit-user-modify", null, null),
"user-select" => array("-moz-user-select", "-webkit-user-select", null, null),
"white-space" => array(__CLASS__, "whiteSpace"),
"window-shadow" => array("-moz-window-shadow", null, null, null),
"word-break" => array(null, null, null, "-ms-word-break"),
"word-wrap" => array(null, null, null, "-ms-word-wrap"),
"writing-mode" => array(null, "-webkit-writing-mode", null, "-ms-writing-mode"),
"zoom" => array(null, null, null, "-ms-zoom")
);
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value large than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$r = 0;
$transformations = &$this->transformations;
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
if (get_class($tokens[$i]) === "CssRulesetDeclarationToken")
{
$tProperty = $tokens[$i]->Property;
if (isset($transformations[$tProperty]))
{
$result = array();
if (is_callable($transformations[$tProperty]))
{
$result = call_user_func_array($transformations[$tProperty], array($tokens[$i]));
if (!is_array($result) && is_object($result))
{
$result = array($result);
}
}
else
{
$tValue = $tokens[$i]->Value;
$tMediaTypes = $tokens[$i]->MediaTypes;
foreach ($transformations[$tProperty] as $property)
{
if ($property !== null)
{
$result[] = new CssRulesetDeclarationToken($property, $tValue, $tMediaTypes);
}
}
}
if (count($result) > 0)
{
array_splice($tokens, $i + 1, 0, $result);
$i += count($result);
$l += count($result);
}
}
}
}
return $r;
}
/**
* Transforms the Internet Explorer specific declaration property "filter" to Internet Explorer 8+ compatible
* declaratiopn property "-ms-filter".
*
* @param aCssToken $token
* @return array
*/
private static function filter($token)
{
$r = array
(
new CssRulesetDeclarationToken("-ms-filter", "\"" . $token->Value . "\"", $token->MediaTypes),
);
return $r;
}
/**
* Transforms "opacity: {value}" into browser specific counterparts.
*
* @param aCssToken $token
* @return array
*/
private static function opacity($token)
{
// Calculate the value for Internet Explorer filter statement
$ieValue = (int) ((float) $token->Value * 100);
$r = array
(
// Internet Explorer >= 8
new CssRulesetDeclarationToken("-ms-filter", "\"alpha(opacity=" . $ieValue . ")\"", $token->MediaTypes),
// Internet Explorer >= 4 <= 7
new CssRulesetDeclarationToken("filter", "alpha(opacity=" . $ieValue . ")", $token->MediaTypes),
new CssRulesetDeclarationToken("zoom", "1", $token->MediaTypes)
);
return $r;
}
/**
* Transforms "white-space: pre-wrap" into browser specific counterparts.
*
* @param aCssToken $token
* @return array
*/
private static function whiteSpace($token)
{
if (strtolower($token->Value) === "pre-wrap")
{
$r = array
(
// Firefox < 3
new CssRulesetDeclarationToken("white-space", "-moz-pre-wrap", $token->MediaTypes),
// Webkit
new CssRulesetDeclarationToken("white-space", "-webkit-pre-wrap", $token->MediaTypes),
// Opera >= 4 <= 6
new CssRulesetDeclarationToken("white-space", "-pre-wrap", $token->MediaTypes),
// Opera >= 7
new CssRulesetDeclarationToken("white-space", "-o-pre-wrap", $token->MediaTypes),
// Internet Explorer >= 5.5
new CssRulesetDeclarationToken("word-wrap", "break-word", $token->MediaTypes)
);
return $r;
}
else
{
return array();
}
}
}
/**
* This {@link aCssMinifierFilter minifier filter} will convert @keyframes at-rule block to browser specific counterparts.
*
* @package CssMin/Minifier/Filters
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssConvertLevel3AtKeyframesMinifierFilter extends aCssMinifierFilter
{
/**
* Implements {@link aCssMinifierFilter::filter()}.
*
* @param array $tokens Array of objects of type aCssToken
* @return integer Count of added, changed or removed tokens; a return value larger than 0 will rebuild the array
*/
public function apply(array &$tokens)
{
$r = 0;
$transformations = array("-moz-keyframes", "-webkit-keyframes");
for ($i = 0, $l = count($tokens); $i < $l; $i++)
{
if (get_class($tokens[$i]) === "CssAtKeyframesStartToken")
{
for ($ii = $i; $ii < $l; $ii++)
{
if (get_class($tokens[$ii]) === "CssAtKeyframesEndToken")
{
break;
}
}
if (get_class($tokens[$ii]) === "CssAtKeyframesEndToken")
{
$add = array();
$source = array();
for ($iii = $i; $iii <= $ii; $iii++)
{
$source[] = clone($tokens[$iii]);
}
foreach ($transformations as $transformation)
{
$t = array();
foreach ($source as $token)
{
$t[] = clone($token);
}
$t[0]->AtRuleName = $transformation;
$add = array_merge($add, $t);
}
if (isset($this->configuration["RemoveSource"]) && $this->configuration["RemoveSource"] === true)
{
array_splice($tokens, $i, $ii - $i + 1, $add);
}
else
{
array_splice($tokens, $ii + 1, 0, $add);
}
$l = count($tokens);
$i = $ii + count($add);
$r += count($add);
}
}
}
return $r;
}
}
/**
* This {@link aCssMinifierPlugin} will convert a color value in hsl notation to hexadecimal notation.
*
* Example:
* <code>
* color: hsl(232,36%,48%);
* </code>
*
* Will get converted to:
* <code>
* color:#4e5aa7;
* </code>
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssConvertHslColorsMinifierPlugin extends aCssMinifierPlugin
{
/**
* Regular expression matching the value.
*
* @var string
*/
private $reMatch = "/^hsl\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*%\s*,\s*([0-9]+)\s*%\s*\)/iS";
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (stripos($token->Value, "hsl") !== false && preg_match($this->reMatch, $token->Value, $m))
{
$token->Value = str_replace($m[0], $this->hsl2hex($m[1], $m[2], $m[3]), $token->Value);
}
return false;
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
/**
* Convert a HSL value to hexadecimal notation.
*
* Based on: {@link http://www.easyrgb.com/index.php?X=MATH&H=19#text19}.
*
* @param integer $hue Hue
* @param integer $saturation Saturation
* @param integer $lightness Lightnesss
* @return string
*/
private function hsl2hex($hue, $saturation, $lightness)
{
$hue = $hue / 360;
$saturation = $saturation / 100;
$lightness = $lightness / 100;
if ($saturation == 0)
{
$red = $lightness * 255;
$green = $lightness * 255;
$blue = $lightness * 255;
}
else
{
if ($lightness < 0.5 )
{
$v2 = $lightness * (1 + $saturation);
}
else
{
$v2 = ($lightness + $saturation) - ($saturation * $lightness);
}
$v1 = 2 * $lightness - $v2;
$red = 255 * self::hue2rgb($v1, $v2, $hue + (1 / 3));
$green = 255 * self::hue2rgb($v1, $v2, $hue);
$blue = 255 * self::hue2rgb($v1, $v2, $hue - (1 / 3));
}
return "#" . str_pad(dechex(round($red)), 2, "0", STR_PAD_LEFT) . str_pad(dechex(round($green)), 2, "0", STR_PAD_LEFT) . str_pad(dechex(round($blue)), 2, "0", STR_PAD_LEFT);
}
/**
* Apply hue to a rgb color value.
*
* @param integer $v1 Value 1
* @param integer $v2 Value 2
* @param integer $hue Hue
* @return integer
*/
private function hue2rgb($v1, $v2, $hue)
{
if ($hue < 0)
{
$hue += 1;
}
if ($hue > 1)
{
$hue -= 1;
}
if ((6 * $hue) < 1)
{
return ($v1 + ($v2 - $v1) * 6 * $hue);
}
if ((2 * $hue) < 1)
{
return ($v2);
}
if ((3 * $hue) < 2)
{
return ($v1 + ($v2 - $v1) * (( 2 / 3) - $hue) * 6);
}
return $v1;
}
}
/**
* This {@link aCssMinifierPlugin} will convert the font-weight values normal and bold to their numeric notation.
*
* Example:
* <code>
* font-weight: normal;
* font: bold 11px monospace;
* </code>
*
* Will get converted to:
* <code>
* font-weight:400;
* font:700 11px monospace;
* </code>
*
* @package CssMin/Minifier/Pluginsn
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssConvertFontWeightMinifierPlugin extends aCssMinifierPlugin
{
/**
* Array of included declaration properties this plugin will process; others declaration properties will get
* ignored.
*
* @var array
*/
private $include = array
(
"font",
"font-weight"
);
/**
* Regular expression matching the value.
*
* @var string
*/
private $reMatch = null;
/**
* Transformation table used by the {@link CssConvertFontWeightMinifierPlugin::reReplace() replacement method}.
*
* @var array
*/
private $transformation = array
(
"normal" => "400",
"bold" => "700"
);
/**
* Overwrites {@link aCssMinifierPlugin::__construct()}.
*
* The constructor will create the {@link CssConvertFontWeightMinifierPlugin::$reMatch replace regular expression}
* based on the {@link CssConvertFontWeightMinifierPlugin::$transformation transformation table}.
*
* @param CssMinifier $minifier The CssMinifier object of this plugin.
* @return void
*/
public function __construct(CssMinifier $minifier)
{
$this->reMatch = "/(^|\s)+(" . implode("|", array_keys($this->transformation)). ")(\s|$)+/iS";
parent::__construct($minifier);
}
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (in_array($token->Property, $this->include) && preg_match($this->reMatch, $token->Value, $m))
{
$token->Value = preg_replace_callback($this->reMatch, array($this, 'reReplace'), $token->Value);
}
return false;
}
/**
* Callback for replacement value.
*
* @param array $match
* @return string
*/
private function reReplace($match)
{
return $match[1] . $this->transformation[strtolower($match[2])] . $match[3];
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
}
/**
* This {@link aCssMinifierPlugin} will compress several unit values to their short notations. Examples:
*
* <code>
* padding: 0.5em;
* border: 0px;
* margin: 0 0 0 0;
* </code>
*
* Will get compressed to:
*
* <code>
* padding:.5px;
* border:0;
* margin:0;
* </code>
*
* --
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssCompressUnitValuesMinifierPlugin extends aCssMinifierPlugin
{
/**
* Regular expression used for matching and replacing unit values.
*
* @var array
*/
private $re = array
(
"/(^| |-)0\.([0-9]+?)(0+)?(%|em|ex|px|in|cm|mm|pt|pc)/iS" => "\${1}.\${2}\${4}",
"/(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)/iS" => "\${1}0",
);
/**
* Regular expression used for matching and replacing unit values only for non-blacklisted declarations.
*
* @var array
*/
private $reBlacklisted = array(
"/(^0\s0\s0\s0)|(^0\s0\s0$)|(^0\s0$)/iS" => "0",
);
/**
* Blacklisted properties for the above regular expression.
*
* @var array
*/
private $propertiesBlacklist = array('background-position');
/**
* Regular expression matching the value.
*
* @var string
*/
private $reMatch = "/(^| |-)0\.([0-9]+?)(0+)?(%|em|ex|px|in|cm|mm|pt|pc)|(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)|(^0\s0\s0\s0$)|(^0\s0\s0$)|(^0\s0$)/iS";
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (preg_match($this->reMatch, $token->Value))
{
foreach ($this->re as $reMatch => $reReplace)
{
$token->Value = preg_replace($reMatch, $reReplace, $token->Value);
}
if (!in_array($token->Property, $this->propertiesBlacklist))
{
foreach ($this->reBlacklisted as $reMatch => $reReplace)
{
$token->Value = preg_replace($reMatch, $reReplace, $token->Value);
}
}
}
return false;
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
}
/**
* This {@link aCssMinifierPlugin} compress the content of expresssion() declaration values.
*
* For compression of expressions {@link https://github.com/rgrove/jsmin-php/ JSMin} will get used. JSMin have to be
* already included or loadable via {@link http://goo.gl/JrW54 PHP autoloading}.
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssCompressExpressionValuesMinifierPlugin extends aCssMinifierPlugin
{
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (class_exists("JSMin") && stripos($token->Value, "expression(") !== false)
{
$value = $token->Value;
$value = substr($token->Value, stripos($token->Value, "expression(") + 10);
$value = trim(JSMin::minify($value));
$token->Value = "expression(" . $value . ")";
}
return false;
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
}
/**
* This {@link aCssMinifierPlugin} will convert hexadecimal color value with 6 chars to their 3 char hexadecimal
* notation (if possible).
*
* Example:
* <code>
* color: #aabbcc;
* </code>
*
* Will get converted to:
* <code>
* color:#abc;
* </code>
*
* @package CssMin/Minifier/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssCompressColorValuesMinifierPlugin extends aCssMinifierPlugin
{
/**
* Regular expression matching 6 char hexadecimal color values.
*
* @var string
*/
private $reMatch = "/\#([0-9a-f]{6})/iS";
/**
* Implements {@link aCssMinifierPlugin::minify()}.
*
* @param aCssToken $token Token to process
* @return boolean Return TRUE to break the processing of this token; FALSE to continue
*/
public function apply(aCssToken &$token)
{
if (strpos($token->Value, "#") !== false && preg_match($this->reMatch, $token->Value, $m))
{
$value = strtolower($m[1]);
if ($value[0] == $value[1] && $value[2] == $value[3] && $value[4] == $value[5])
{
$token->Value = str_replace($m[0], "#" . $value[0] . $value[2] . $value[4], $token->Value);
}
}
return false;
}
/**
* Implements {@link aMinifierPlugin::getTriggerTokens()}
*
* @return array
*/
public function getTriggerTokens()
{
return array
(
"CssAtFontFaceDeclarationToken",
"CssAtPageDeclarationToken",
"CssRulesetDeclarationToken"
);
}
}
/**
* This {@link aCssToken CSS token} represents a CSS comment.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssCommentToken extends aCssToken
{
/**
* Comment as Text.
*
* @var string
*/
public $Comment = "";
/**
* Set the properties of a comment token.
*
* @param string $comment Comment including comment delimiters
* @return void
*/
public function __construct($comment)
{
$this->Comment = $comment;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return $this->Comment;
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing comments.
*
* Adds a {@link CssCommentToken} to the parser if a comment was found.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssCommentParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("*", "/");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return false;
}
/**
* Stored buffer for restore.
*
* @var string
*/
private $restoreBuffer = "";
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
if ($char === "*" && $previousChar === "/" && $state !== "T_COMMENT")
{
$this->parser->pushState("T_COMMENT");
$this->parser->setExclusive(__CLASS__);
$this->restoreBuffer = substr($this->parser->getAndClearBuffer(), 0, -2);
}
elseif ($char === "/" && $previousChar === "*" && $state === "T_COMMENT")
{
$this->parser->popState();
$this->parser->unsetExclusive();
$this->parser->appendToken(new CssCommentToken("/*" . $this->parser->getAndClearBuffer()));
$this->parser->setBuffer($this->restoreBuffer);
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the start of a @variables at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtVariablesStartToken extends aCssAtBlockStartToken
{
/**
* Media types of the @variables at-rule block.
*
* @var array
*/
public $MediaTypes = array();
/**
* Set the properties of a @variables at-rule token.
*
* @param array $mediaTypes Media types
* @return void
*/
public function __construct($mediaTypes = null)
{
$this->MediaTypes = $mediaTypes ? $mediaTypes : array("all");
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @variables at-rule block with including declarations.
*
* Found @variables at-rule blocks will add a {@link CssAtVariablesStartToken} and {@link CssAtVariablesEndToken} to the
* parser; including declarations as {@link CssAtVariablesDeclarationToken}.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtVariablesParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", "{", "}", ":", ";");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_VARIABLES::PREPARE", "T_AT_VARIABLES", "T_AT_VARIABLES_DECLARATION");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of @variables at-rule block
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 10)) === "@variables")
{
$this->parser->pushState("T_AT_VARIABLES::PREPARE");
$this->parser->clearBuffer();
return $index + 10;
}
// Start of @variables declarations
elseif ($char === "{" && $state === "T_AT_VARIABLES::PREPARE")
{
$this->parser->setState("T_AT_VARIABLES");
$mediaTypes = array_filter(array_map("trim", explode(",", $this->parser->getAndClearBuffer("{"))));
$this->parser->appendToken(new CssAtVariablesStartToken($mediaTypes));
}
// Start of @variables declaration
if ($char === ":" && $state === "T_AT_VARIABLES")
{
$this->buffer = $this->parser->getAndClearBuffer(":");
$this->parser->pushState("T_AT_VARIABLES_DECLARATION");
}
// Unterminated @variables declaration
elseif ($char === ":" && $state === "T_AT_VARIABLES_DECLARATION")
{
// Ignore Internet Explorer filter declarations
if ($this->buffer === "filter")
{
return false;
}
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @variables declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_"));
}
// End of @variables declaration
elseif (($char === ";" || $char === "}") && $state === "T_AT_VARIABLES_DECLARATION")
{
$value = $this->parser->getAndClearBuffer(";}");
if (strtolower(substr($value, -10, 10)) === "!important")
{
$value = trim(substr($value, 0, -10));
$isImportant = true;
}
else
{
$isImportant = false;
}
$this->parser->popState();
$this->parser->appendToken(new CssAtVariablesDeclarationToken($this->buffer, $value, $isImportant));
$this->buffer = "";
}
// End of @variables at-rule block
elseif ($char === "}" && $state === "T_AT_VARIABLES")
{
$this->parser->popState();
$this->parser->clearBuffer();
$this->parser->appendToken(new CssAtVariablesEndToken());
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the end of a @variables at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtVariablesEndToken extends aCssAtBlockEndToken
{
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "";
}
}
/**
* This {@link aCssToken CSS token} represents a declaration of a @variables at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtVariablesDeclarationToken extends aCssDeclarationToken
{
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "";
}
}
/**
* This {@link aCssToken CSS token} represents the start of a @page at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtPageStartToken extends aCssAtBlockStartToken
{
/**
* Selector.
*
* @var string
*/
public $Selector = "";
/**
* Sets the properties of the @page at-rule.
*
* @param string $selector Selector
* @return void
*/
public function __construct($selector = "")
{
$this->Selector = $selector;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "@page" . ($this->Selector ? " " . $this->Selector : "") . "{";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @page at-rule block with including declarations.
*
* Found @page at-rule blocks will add a {@link CssAtPageStartToken} and {@link CssAtPageEndToken} to the
* parser; including declarations as {@link CssAtPageDeclarationToken}.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtPageParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", "{", "}", ":", ";");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_PAGE::SELECTOR", "T_AT_PAGE", "T_AT_PAGE_DECLARATION");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of @page at-rule block
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 5)) === "@page")
{
$this->parser->pushState("T_AT_PAGE::SELECTOR");
$this->parser->clearBuffer();
return $index + 5;
}
// Start of @page declarations
elseif ($char === "{" && $state === "T_AT_PAGE::SELECTOR")
{
$selector = $this->parser->getAndClearBuffer("{");
$this->parser->setState("T_AT_PAGE");
$this->parser->clearBuffer();
$this->parser->appendToken(new CssAtPageStartToken($selector));
}
// Start of @page declaration
elseif ($char === ":" && $state === "T_AT_PAGE")
{
$this->parser->pushState("T_AT_PAGE_DECLARATION");
$this->buffer = $this->parser->getAndClearBuffer(":", true);
}
// Unterminated @font-face declaration
elseif ($char === ":" && $state === "T_AT_PAGE_DECLARATION")
{
// Ignore Internet Explorer filter declarations
if ($this->buffer === "filter")
{
return false;
}
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @page declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_"));
}
// End of @page declaration
elseif (($char === ";" || $char === "}") && $state == "T_AT_PAGE_DECLARATION")
{
$value = $this->parser->getAndClearBuffer(";}");
if (strtolower(substr($value, -10, 10)) == "!important")
{
$value = trim(substr($value, 0, -10));
$isImportant = true;
}
else
{
$isImportant = false;
}
$this->parser->popState();
$this->parser->appendToken(new CssAtPageDeclarationToken($this->buffer, $value, $isImportant));
// --
if ($char === "}")
{
$this->parser->popState();
$this->parser->appendToken(new CssAtPageEndToken());
}
$this->buffer = "";
}
// End of @page at-rule block
elseif ($char === "}" && $state === "T_AT_PAGE")
{
$this->parser->popState();
$this->parser->clearBuffer();
$this->parser->appendToken(new CssAtPageEndToken());
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the end of a @page at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtPageEndToken extends aCssAtBlockEndToken
{
}
/**
* This {@link aCssToken CSS token} represents a declaration of a @page at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtPageDeclarationToken extends aCssDeclarationToken
{
}
/**
* This {@link aCssToken CSS token} represents the start of a @media at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtMediaStartToken extends aCssAtBlockStartToken
{
/**
* Sets the properties of the @media at-rule.
*
* @param array $mediaTypes Media types
* @return void
*/
public function __construct(array $mediaTypes = array())
{
$this->MediaTypes = $mediaTypes;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "@media " . implode(",", $this->MediaTypes) . "{";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @media at-rule block.
*
* Found @media at-rule blocks will add a {@link CssAtMediaStartToken} and {@link CssAtMediaEndToken} to the parser.
* This plugin will also set the the current media types using {@link CssParser::setMediaTypes()} and
* {@link CssParser::unsetMediaTypes()}.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtMediaParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", "{", "}");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_MEDIA::PREPARE", "T_AT_MEDIA");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 6)) === "@media")
{
$this->parser->pushState("T_AT_MEDIA::PREPARE");
$this->parser->clearBuffer();
return $index + 6;
}
elseif ($char === "{" && $state === "T_AT_MEDIA::PREPARE")
{
$mediaTypes = array_filter(array_map("trim", explode(",", $this->parser->getAndClearBuffer("{"))));
$this->parser->setMediaTypes($mediaTypes);
$this->parser->setState("T_AT_MEDIA");
$this->parser->appendToken(new CssAtMediaStartToken($mediaTypes));
}
elseif ($char === "}" && $state === "T_AT_MEDIA")
{
$this->parser->appendToken(new CssAtMediaEndToken());
$this->parser->clearBuffer();
$this->parser->unsetMediaTypes();
$this->parser->popState();
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the end of a @media at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtMediaEndToken extends aCssAtBlockEndToken
{
}
/**
* This {@link aCssToken CSS token} represents the start of a @keyframes at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtKeyframesStartToken extends aCssAtBlockStartToken
{
/**
* Name of the at-rule.
*
* @var string
*/
public $AtRuleName = "keyframes";
/**
* Name
*
* @var string
*/
public $Name = "";
/**
* Sets the properties of the @page at-rule.
*
* @param string $selector Selector
* @return void
*/
public function __construct($name, $atRuleName = null)
{
$this->Name = $name;
if (!is_null($atRuleName))
{
$this->AtRuleName = $atRuleName;
}
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
if ($this->AtRuleName === "-moz-keyframes")
{
return "@-moz-keyframes " . $this->Name . " {";
}
return "@" . $this->AtRuleName . " " . $this->Name . "{";
}
}
/**
* This {@link aCssToken CSS token} represents the start of a ruleset of a @keyframes at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtKeyframesRulesetStartToken extends aCssRulesetStartToken
{
/**
* Array of selectors.
*
* @var array
*/
public $Selectors = array();
/**
* Set the properties of a ruleset token.
*
* @param array $selectors Selectors of the ruleset
* @return void
*/
public function __construct(array $selectors = array())
{
$this->Selectors = $selectors;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return implode(",", $this->Selectors) . "{";
}
}
/**
* This {@link aCssToken CSS token} represents the end of a ruleset of a @keyframes at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtKeyframesRulesetEndToken extends aCssRulesetEndToken
{
}
/**
* This {@link aCssToken CSS token} represents a ruleset declaration of a @keyframes at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtKeyframesRulesetDeclarationToken extends aCssDeclarationToken
{
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @keyframes at-rule blocks, rulesets and declarations.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtKeyframesParserPlugin extends aCssParserPlugin
{
/**
* @var string Keyword
*/
private $atRuleName = "";
/**
* Selectors.
*
* @var array
*/
private $selectors = array();
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", "{", "}", ":", ",", ";");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_KEYFRAMES::NAME", "T_AT_KEYFRAMES", "T_AT_KEYFRAMES_RULESETS", "T_AT_KEYFRAMES_RULESET", "T_AT_KEYFRAMES_RULESET_DECLARATION");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of @keyframes at-rule block
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 10)) === "@keyframes")
{
$this->atRuleName = "keyframes";
$this->parser->pushState("T_AT_KEYFRAMES::NAME");
$this->parser->clearBuffer();
return $index + 10;
}
// Start of @keyframes at-rule block (@-moz-keyframes)
elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 15)) === "@-moz-keyframes")
{
$this->atRuleName = "-moz-keyframes";
$this->parser->pushState("T_AT_KEYFRAMES::NAME");
$this->parser->clearBuffer();
return $index + 15;
}
// Start of @keyframes at-rule block (@-webkit-keyframes)
elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 18)) === "@-webkit-keyframes")
{
$this->atRuleName = "-webkit-keyframes";
$this->parser->pushState("T_AT_KEYFRAMES::NAME");
$this->parser->clearBuffer();
return $index + 18;
}
// Start of @keyframes at-rule block (@-o-keyframes)
elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 13)) === "@-o-keyframes")
{
$this->atRuleName = "-o-keyframes";
$this->parser->pushState("T_AT_KEYFRAMES::NAME");
$this->parser->clearBuffer();
return $index + 13;
}
// Start of @keyframes at-rule block (@-ms-keyframes)
elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 14)) === "@-ms-keyframes")
{
$this->atRuleName = "-ms-keyframes";
$this->parser->pushState("T_AT_KEYFRAMES::NAME");
$this->parser->clearBuffer();
return $index + 14;
}
// Start of @keyframes rulesets
elseif ($char === "{" && $state === "T_AT_KEYFRAMES::NAME")
{
$name = $this->parser->getAndClearBuffer("{\"'");
$this->parser->setState("T_AT_KEYFRAMES_RULESETS");
$this->parser->clearBuffer();
$this->parser->appendToken(new CssAtKeyframesStartToken($name, $this->atRuleName));
}
// Start of @keyframe ruleset and selectors
if ($char === "," && $state === "T_AT_KEYFRAMES_RULESETS")
{
$this->selectors[] = $this->parser->getAndClearBuffer(",{");
}
// Start of a @keyframes ruleset
elseif ($char === "{" && $state === "T_AT_KEYFRAMES_RULESETS")
{
if ($this->parser->getBuffer() !== "")
{
$this->selectors[] = $this->parser->getAndClearBuffer(",{");
$this->parser->pushState("T_AT_KEYFRAMES_RULESET");
$this->parser->appendToken(new CssAtKeyframesRulesetStartToken($this->selectors));
$this->selectors = array();
}
}
// Start of @keyframes ruleset declaration
elseif ($char === ":" && $state === "T_AT_KEYFRAMES_RULESET")
{
$this->parser->pushState("T_AT_KEYFRAMES_RULESET_DECLARATION");
$this->buffer = $this->parser->getAndClearBuffer(":;", true);
}
// Unterminated @keyframes ruleset declaration
elseif ($char === ":" && $state === "T_AT_KEYFRAMES_RULESET_DECLARATION")
{
// Ignore Internet Explorer filter declarations
if ($this->buffer === "filter")
{
return false;
}
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @keyframes ruleset declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_"));
}
// End of declaration
elseif (($char === ";" || $char === "}") && $state === "T_AT_KEYFRAMES_RULESET_DECLARATION")
{
$value = $this->parser->getAndClearBuffer(";}");
if (strtolower(substr($value, -10, 10)) === "!important")
{
$value = trim(substr($value, 0, -10));
$isImportant = true;
}
else
{
$isImportant = false;
}
$this->parser->popState();
$this->parser->appendToken(new CssAtKeyframesRulesetDeclarationToken($this->buffer, $value, $isImportant));
// Declaration ends with a right curly brace; so we have to end the ruleset
if ($char === "}")
{
$this->parser->appendToken(new CssAtKeyframesRulesetEndToken());
$this->parser->popState();
}
$this->buffer = "";
}
// End of @keyframes ruleset
elseif ($char === "}" && $state === "T_AT_KEYFRAMES_RULESET")
{
$this->parser->clearBuffer();
$this->parser->popState();
$this->parser->appendToken(new CssAtKeyframesRulesetEndToken());
}
// End of @keyframes rulesets
elseif ($char === "}" && $state === "T_AT_KEYFRAMES_RULESETS")
{
$this->parser->clearBuffer();
$this->parser->popState();
$this->parser->appendToken(new CssAtKeyframesEndToken());
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the end of a @keyframes at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtKeyframesEndToken extends aCssAtBlockEndToken
{
}
/**
* This {@link aCssToken CSS token} represents a @import at-rule.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1.b1 (2001-02-22)
*/
class CssAtImportToken extends aCssToken
{
/**
* Import path of the @import at-rule.
*
* @var string
*/
public $Import = "";
/**
* Media types of the @import at-rule.
*
* @var array
*/
public $MediaTypes = array();
/**
* Set the properties of a @import at-rule token.
*
* @param string $import Import path
* @param array $mediaTypes Media types
* @return void
*/
public function __construct($import, $mediaTypes)
{
$this->Import = $import;
$this->MediaTypes = $mediaTypes ? $mediaTypes : array();
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "@import \"" . $this->Import . "\"" . (count($this->MediaTypes) > 0 ? " " . implode(",", $this->MediaTypes) : ""). ";";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @import at-rule.
*
* If a @import at-rule was found this plugin will add a {@link CssAtImportToken} to the parser.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtImportParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", ";", ",", "\n");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_IMPORT");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 7)) === "@import")
{
$this->parser->pushState("T_AT_IMPORT");
$this->parser->clearBuffer();
return $index + 7;
}
elseif (($char === ";" || $char === "\n") && $state === "T_AT_IMPORT")
{
$this->buffer = $this->parser->getAndClearBuffer(";");
$pos = false;
foreach (array(")", "\"", "'") as $needle)
{
if (($pos = strrpos($this->buffer, $needle)) !== false)
{
break;
}
}
$import = substr($this->buffer, 0, $pos + 1);
if (stripos($import, "url(") === 0)
{
$import = substr($import, 4, -1);
}
$import = trim($import, " \t\n\r\0\x0B'\"");
$mediaTypes = array_filter(array_map("trim", explode(",", trim(substr($this->buffer, $pos + 1), " \t\n\r\0\x0B{"))));
if ($pos)
{
$this->parser->appendToken(new CssAtImportToken($import, $mediaTypes));
}
else
{
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Invalid @import at-rule syntax", $this->buffer));
}
$this->parser->popState();
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the start of a @font-face at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtFontFaceStartToken extends aCssAtBlockStartToken
{
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "@font-face{";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @font-face at-rule block with including declarations.
*
* Found @font-face at-rule blocks will add a {@link CssAtFontFaceStartToken} and {@link CssAtFontFaceEndToken} to the
* parser; including declarations as {@link CssAtFontFaceDeclarationToken}.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtFontFaceParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", "{", "}", ":", ";");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_FONT_FACE::PREPARE", "T_AT_FONT_FACE", "T_AT_FONT_FACE_DECLARATION");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
// Start of @font-face at-rule block
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 10)) === "@font-face")
{
$this->parser->pushState("T_AT_FONT_FACE::PREPARE");
$this->parser->clearBuffer();
return $index + 10 - 1;
}
// Start of @font-face declarations
elseif ($char === "{" && $state === "T_AT_FONT_FACE::PREPARE")
{
$this->parser->setState("T_AT_FONT_FACE");
$this->parser->clearBuffer();
$this->parser->appendToken(new CssAtFontFaceStartToken());
}
// Start of @font-face declaration
elseif ($char === ":" && $state === "T_AT_FONT_FACE")
{
$this->parser->pushState("T_AT_FONT_FACE_DECLARATION");
$this->buffer = $this->parser->getAndClearBuffer(":", true);
}
// Unterminated @font-face declaration
elseif ($char === ":" && $state === "T_AT_FONT_FACE_DECLARATION")
{
// Ignore Internet Explorer filter declarations
if ($this->buffer === "filter")
{
return false;
}
CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @font-face declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_"));
}
// End of @font-face declaration
elseif (($char === ";" || $char === "}") && $state === "T_AT_FONT_FACE_DECLARATION")
{
$value = $this->parser->getAndClearBuffer(";}");
if (strtolower(substr($value, -10, 10)) === "!important")
{
$value = trim(substr($value, 0, -10));
$isImportant = true;
}
else
{
$isImportant = false;
}
$this->parser->popState();
$this->parser->appendToken(new CssAtFontFaceDeclarationToken($this->buffer, $value, $isImportant));
$this->buffer = "";
// --
if ($char === "}")
{
$this->parser->appendToken(new CssAtFontFaceEndToken());
$this->parser->popState();
}
}
// End of @font-face at-rule block
elseif ($char === "}" && $state === "T_AT_FONT_FACE")
{
$this->parser->appendToken(new CssAtFontFaceEndToken());
$this->parser->clearBuffer();
$this->parser->popState();
}
else
{
return false;
}
return true;
}
}
/**
* This {@link aCssToken CSS token} represents the end of a @font-face at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtFontFaceEndToken extends aCssAtBlockEndToken
{
}
/**
* This {@link aCssToken CSS token} represents a declaration of a @font-face at-rule block.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtFontFaceDeclarationToken extends aCssDeclarationToken
{
}
/**
* This {@link aCssToken CSS token} represents a @charset at-rule.
*
* @package CssMin/Tokens
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtCharsetToken extends aCssToken
{
/**
* Charset of the @charset at-rule.
*
* @var string
*/
public $Charset = "";
/**
* Set the properties of @charset at-rule token.
*
* @param string $charset Charset of the @charset at-rule token
* @return void
*/
public function __construct($charset)
{
$this->Charset = $charset;
}
/**
* Implements {@link aCssToken::__toString()}.
*
* @return string
*/
public function __toString()
{
return "@charset " . $this->Charset . ";";
}
}
/**
* {@link aCssParserPlugin Parser plugin} for parsing @charset at-rule.
*
* If a @charset at-rule was found this plugin will add a {@link CssAtCharsetToken} to the parser.
*
* @package CssMin/Parser/Plugins
* @link http://code.google.com/p/cssmin/
* @author Joe Scylla <joe.scylla@gmail.com>
* @copyright 2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
* @license http://opensource.org/licenses/mit-license.php MIT License
* @version 3.0.1
*/
class CssAtCharsetParserPlugin extends aCssParserPlugin
{
/**
* Implements {@link aCssParserPlugin::getTriggerChars()}.
*
* @return array
*/
public function getTriggerChars()
{
return array("@", ";", "\n");
}
/**
* Implements {@link aCssParserPlugin::getTriggerStates()}.
*
* @return array
*/
public function getTriggerStates()
{
return array("T_DOCUMENT", "T_AT_CHARSET");
}
/**
* Implements {@link aCssParserPlugin::parse()}.
*
* @param integer $index Current index
* @param string $char Current char
* @param string $previousChar Previous char
* @return mixed TRUE will break the processing; FALSE continue with the next plugin; integer set a new index and break the processing
*/
public function parse($index, $char, $previousChar, $state)
{
if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 8)) === "@charset")
{
$this->parser->pushState("T_AT_CHARSET");
$this->parser->clearBuffer();
return $index + 8;
}
elseif (($char === ";" || $char === "\n") && $state === "T_AT_CHARSET")
{
$charset = $this->parser->getAndClearBuffer(";");
$this->parser->popState();
$this->parser->appendToken(new CssAtCharsetToken($charset));
}
else
{
return false;
}
return true;
}
}
?>
<?php
/**
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace Interop\Container;
use Psr\Container\ContainerInterface as PsrContainerInterface;
/**
* Describes the interface of a container that exposes methods to read its entries.
*/
interface ContainerInterface extends PsrContainerInterface
{
}
<?php
/**
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace Interop\Container\Exception;
use Psr\Container\ContainerExceptionInterface as PsrContainerException;
/**
* Base interface representing a generic exception in a container.
*/
interface ContainerException extends PsrContainerException
{
}
<?php
/**
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace Interop\Container\Exception;
use Psr\Container\NotFoundExceptionInterface as PsrNotFoundException;
/**
* No entry was found in the container.
*/
interface NotFoundException extends ContainerException, PsrNotFoundException
{
}
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Dflydev\\DotAccessData' => array($vendorDir . '/dflydev/dot-access-data/src'),
'Console' => array($vendorDir . '/pear/console_getopt'),
'Archive_Tar' => array($vendorDir . '/pear/archive_tar'),
'' => array($vendorDir . '/pear/pear-core-minimal/src'),
);
<?php
// include_paths.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
$vendorDir . '/pear/archive_tar',
$vendorDir . '/pear/console_getopt',
$vendorDir . '/pear/pear-core-minimal/src',
$vendorDir . '/pear/pear_exception',
);
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'),
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'SelfUpdate\\' => array($vendorDir . '/consolidation/self-update/src'),
'Robo\\' => array($baseDir . '/src', $baseDir . '/tests/src'),
'RoboExample\\' => array($baseDir . '/examples/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'Patchwork\\' => array($vendorDir . '/patchwork/jsqueeze/src'),
'League\\Container\\' => array($vendorDir . '/league/container/src'),
'Interop\\Container\\' => array($vendorDir . '/container-interop/container-interop/src/Interop/Container'),
'Grasmash\\YamlExpander\\' => array($vendorDir . '/grasmash/yaml-expander/src'),
'Grasmash\\Expander\\' => array($vendorDir . '/grasmash/expander/src'),
'Consolidation\\OutputFormatters\\' => array($vendorDir . '/consolidation/output-formatters/src'),
'Consolidation\\Log\\' => array($vendorDir . '/consolidation/log/src'),
'Consolidation\\Config\\' => array($vendorDir . '/consolidation/config/src'),
'Consolidation\\AnnotatedCommand\\' => array($vendorDir . '/consolidation/annotated-command/src'),
);
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Archive_Tar' => $vendorDir . '/pear/archive_tar/Archive/Tar.php',
'Console_Getopt' => $vendorDir . '/pear/console_getopt/Console/Getopt.php',
'Consolidation\\AnnotatedCommand\\AnnotatedCommand' => $vendorDir . '/consolidation/annotated-command/src/AnnotatedCommand.php',
'Consolidation\\AnnotatedCommand\\AnnotatedCommandFactory' => $vendorDir . '/consolidation/annotated-command/src/AnnotatedCommandFactory.php',
'Consolidation\\AnnotatedCommand\\AnnotationData' => $vendorDir . '/consolidation/annotated-command/src/AnnotationData.php',
'Consolidation\\AnnotatedCommand\\Cache\\CacheWrapper' => $vendorDir . '/consolidation/annotated-command/src/Cache/CacheWrapper.php',
'Consolidation\\AnnotatedCommand\\Cache\\NullCache' => $vendorDir . '/consolidation/annotated-command/src/Cache/NullCache.php',
'Consolidation\\AnnotatedCommand\\Cache\\SimpleCacheInterface' => $vendorDir . '/consolidation/annotated-command/src/Cache/SimpleCacheInterface.php',
'Consolidation\\AnnotatedCommand\\CommandCreationListener' => $vendorDir . '/consolidation/annotated-command/src/CommandCreationListener.php',
'Consolidation\\AnnotatedCommand\\CommandCreationListenerInterface' => $vendorDir . '/consolidation/annotated-command/src/CommandCreationListenerInterface.php',
'Consolidation\\AnnotatedCommand\\CommandData' => $vendorDir . '/consolidation/annotated-command/src/CommandData.php',
'Consolidation\\AnnotatedCommand\\CommandError' => $vendorDir . '/consolidation/annotated-command/src/CommandError.php',
'Consolidation\\AnnotatedCommand\\CommandFileDiscovery' => $vendorDir . '/consolidation/annotated-command/src/CommandFileDiscovery.php',
'Consolidation\\AnnotatedCommand\\CommandInfoAltererInterface' => $vendorDir . '/consolidation/annotated-command/src/CommandInfoAltererInterface.php',
'Consolidation\\AnnotatedCommand\\CommandProcessor' => $vendorDir . '/consolidation/annotated-command/src/CommandProcessor.php',
'Consolidation\\AnnotatedCommand\\CommandResult' => $vendorDir . '/consolidation/annotated-command/src/CommandResult.php',
'Consolidation\\AnnotatedCommand\\Events\\CustomEventAwareInterface' => $vendorDir . '/consolidation/annotated-command/src/Events/CustomEventAwareInterface.php',
'Consolidation\\AnnotatedCommand\\Events\\CustomEventAwareTrait' => $vendorDir . '/consolidation/annotated-command/src/Events/CustomEventAwareTrait.php',
'Consolidation\\AnnotatedCommand\\ExitCodeInterface' => $vendorDir . '/consolidation/annotated-command/src/ExitCodeInterface.php',
'Consolidation\\AnnotatedCommand\\Help\\HelpCommand' => $vendorDir . '/consolidation/annotated-command/src/Help/HelpCommand.php',
'Consolidation\\AnnotatedCommand\\Help\\HelpDocument' => $vendorDir . '/consolidation/annotated-command/src/Help/HelpDocument.php',
'Consolidation\\AnnotatedCommand\\Help\\HelpDocumentAlter' => $vendorDir . '/consolidation/annotated-command/src/Help/HelpDocumentAlter.php',
'Consolidation\\AnnotatedCommand\\Help\\HelpDocumentBuilder' => $vendorDir . '/consolidation/annotated-command/src/Help/HelpDocumentBuilder.php',
'Consolidation\\AnnotatedCommand\\Hooks\\AlterResultInterface' => $vendorDir . '/consolidation/annotated-command/src/Hooks/AlterResultInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\CommandEventHookDispatcher' => $vendorDir . '/consolidation/annotated-command/src/Hooks/Dispatchers/CommandEventHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\ExtracterHookDispatcher' => $vendorDir . '/consolidation/annotated-command/src/Hooks/Dispatchers/ExtracterHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\HookDispatcher' => $vendorDir . '/consolidation/annotated-command/src/Hooks/Dispatchers/HookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\InitializeHookDispatcher' => $vendorDir . '/consolidation/annotated-command/src/Hooks/Dispatchers/InitializeHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\InteractHookDispatcher' => $vendorDir . '/consolidation/annotated-command/src/Hooks/Dispatchers/InteractHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\OptionsHookDispatcher' => $vendorDir . '/consolidation/annotated-command/src/Hooks/Dispatchers/OptionsHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\ProcessResultHookDispatcher' => $vendorDir . '/consolidation/annotated-command/src/Hooks/Dispatchers/ProcessResultHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\ReplaceCommandHookDispatcher' => $vendorDir . '/consolidation/annotated-command/src/Hooks/Dispatchers/ReplaceCommandHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\StatusDeterminerHookDispatcher' => $vendorDir . '/consolidation/annotated-command/src/Hooks/Dispatchers/StatusDeterminerHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\ValidateHookDispatcher' => $vendorDir . '/consolidation/annotated-command/src/Hooks/Dispatchers/ValidateHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\ExtractOutputInterface' => $vendorDir . '/consolidation/annotated-command/src/Hooks/ExtractOutputInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\HookManager' => $vendorDir . '/consolidation/annotated-command/src/Hooks/HookManager.php',
'Consolidation\\AnnotatedCommand\\Hooks\\InitializeHookInterface' => $vendorDir . '/consolidation/annotated-command/src/Hooks/InitializeHookInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\InteractorInterface' => $vendorDir . '/consolidation/annotated-command/src/Hooks/InteractorInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\OptionHookInterface' => $vendorDir . '/consolidation/annotated-command/src/Hooks/OptionHookInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\ProcessResultInterface' => $vendorDir . '/consolidation/annotated-command/src/Hooks/ProcessResultInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\StatusDeterminerInterface' => $vendorDir . '/consolidation/annotated-command/src/Hooks/StatusDeterminerInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\ValidatorInterface' => $vendorDir . '/consolidation/annotated-command/src/Hooks/ValidatorInterface.php',
'Consolidation\\AnnotatedCommand\\Input\\StdinAwareInterface' => $vendorDir . '/consolidation/annotated-command/src/Input/StdinAwareInterface.php',
'Consolidation\\AnnotatedCommand\\Input\\StdinAwareTrait' => $vendorDir . '/consolidation/annotated-command/src/Input/StdinAwareTrait.php',
'Consolidation\\AnnotatedCommand\\Input\\StdinHandler' => $vendorDir . '/consolidation/annotated-command/src/Input/StdinHandler.php',
'Consolidation\\AnnotatedCommand\\Options\\AlterOptionsCommandEvent' => $vendorDir . '/consolidation/annotated-command/src/Options/AlterOptionsCommandEvent.php',
'Consolidation\\AnnotatedCommand\\Options\\AutomaticOptionsProviderInterface' => $vendorDir . '/consolidation/annotated-command/src/Options/AutomaticOptionsProviderInterface.php',
'Consolidation\\AnnotatedCommand\\Options\\PrepareFormatter' => $vendorDir . '/consolidation/annotated-command/src/Options/PrepareFormatter.php',
'Consolidation\\AnnotatedCommand\\Options\\PrepareTerminalWidthOption' => $vendorDir . '/consolidation/annotated-command/src/Options/PrepareTerminalWidthOption.php',
'Consolidation\\AnnotatedCommand\\OutputDataInterface' => $vendorDir . '/consolidation/annotated-command/src/OutputDataInterface.php',
'Consolidation\\AnnotatedCommand\\ParameterInjection' => $vendorDir . '/consolidation/annotated-command/src/ParameterInjection.php',
'Consolidation\\AnnotatedCommand\\ParameterInjector' => $vendorDir . '/consolidation/annotated-command/src/ParameterInjector.php',
'Consolidation\\AnnotatedCommand\\Parser\\CommandInfo' => $vendorDir . '/consolidation/annotated-command/src/Parser/CommandInfo.php',
'Consolidation\\AnnotatedCommand\\Parser\\CommandInfoDeserializer' => $vendorDir . '/consolidation/annotated-command/src/Parser/CommandInfoDeserializer.php',
'Consolidation\\AnnotatedCommand\\Parser\\CommandInfoSerializer' => $vendorDir . '/consolidation/annotated-command/src/Parser/CommandInfoSerializer.php',
'Consolidation\\AnnotatedCommand\\Parser\\DefaultsWithDescriptions' => $vendorDir . '/consolidation/annotated-command/src/Parser/DefaultsWithDescriptions.php',
'Consolidation\\AnnotatedCommand\\Parser\\Internal\\BespokeDocBlockParser' => $vendorDir . '/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php',
'Consolidation\\AnnotatedCommand\\Parser\\Internal\\CommandDocBlockParserFactory' => $vendorDir . '/consolidation/annotated-command/src/Parser/Internal/CommandDocBlockParserFactory.php',
'Consolidation\\AnnotatedCommand\\Parser\\Internal\\CsvUtils' => $vendorDir . '/consolidation/annotated-command/src/Parser/Internal/CsvUtils.php',
'Consolidation\\AnnotatedCommand\\Parser\\Internal\\DocblockTag' => $vendorDir . '/consolidation/annotated-command/src/Parser/Internal/DocblockTag.php',
'Consolidation\\AnnotatedCommand\\Parser\\Internal\\FullyQualifiedClassCache' => $vendorDir . '/consolidation/annotated-command/src/Parser/Internal/FullyQualifiedClassCache.php',
'Consolidation\\AnnotatedCommand\\Parser\\Internal\\TagFactory' => $vendorDir . '/consolidation/annotated-command/src/Parser/Internal/TagFactory.php',
'Consolidation\\AnnotatedCommand\\ResultWriter' => $vendorDir . '/consolidation/annotated-command/src/ResultWriter.php',
'Consolidation\\Config\\Config' => $vendorDir . '/consolidation/config/src/Config.php',
'Consolidation\\Config\\ConfigAwareInterface' => $vendorDir . '/consolidation/config/src/ConfigAwareInterface.php',
'Consolidation\\Config\\ConfigAwareTrait' => $vendorDir . '/consolidation/config/src/ConfigAwareTrait.php',
'Consolidation\\Config\\ConfigInterface' => $vendorDir . '/consolidation/config/src/ConfigInterface.php',
'Consolidation\\Config\\GlobalOptionDefaultValuesInterface' => $vendorDir . '/consolidation/config/src/GlobalOptionDefaultValuesInterface.php',
'Consolidation\\Config\\Inject\\ConfigForCommand' => $vendorDir . '/consolidation/config/src/Inject/ConfigForCommand.php',
'Consolidation\\Config\\Inject\\ConfigForSetters' => $vendorDir . '/consolidation/config/src/Inject/ConfigForSetters.php',
'Consolidation\\Config\\Loader\\ConfigLoader' => $vendorDir . '/consolidation/config/src/Loader/ConfigLoader.php',
'Consolidation\\Config\\Loader\\ConfigLoaderInterface' => $vendorDir . '/consolidation/config/src/Loader/ConfigLoaderInterface.php',
'Consolidation\\Config\\Loader\\ConfigProcessor' => $vendorDir . '/consolidation/config/src/Loader/ConfigProcessor.php',
'Consolidation\\Config\\Loader\\YamlConfigLoader' => $vendorDir . '/consolidation/config/src/Loader/YamlConfigLoader.php',
'Consolidation\\Config\\Util\\ArrayUtil' => $vendorDir . '/consolidation/config/src/Util/ArrayUtil.php',
'Consolidation\\Config\\Util\\ConfigFallback' => $vendorDir . '/consolidation/config/src/Util/ConfigFallback.php',
'Consolidation\\Config\\Util\\ConfigGroup' => $vendorDir . '/consolidation/config/src/Util/ConfigGroup.php',
'Consolidation\\Config\\Util\\ConfigInterpolatorInterface' => $vendorDir . '/consolidation/config/src/Util/ConfigInterpolatorInterface.php',
'Consolidation\\Config\\Util\\ConfigInterpolatorTrait' => $vendorDir . '/consolidation/config/src/Util/ConfigInterpolatorTrait.php',
'Consolidation\\Config\\Util\\ConfigMerge' => $vendorDir . '/consolidation/config/src/Util/ConfigMerge.php',
'Consolidation\\Config\\Util\\ConfigOverlay' => $vendorDir . '/consolidation/config/src/Util/ConfigOverlay.php',
'Consolidation\\Config\\Util\\ConfigRuntimeInterface' => $vendorDir . '/consolidation/config/src/Util/ConfigRuntimeInterface.php',
'Consolidation\\Config\\Util\\EnvConfig' => $vendorDir . '/consolidation/config/src/Util/EnvConfig.php',
'Consolidation\\Config\\Util\\Interpolator' => $vendorDir . '/consolidation/config/src/Util/Interpolator.php',
'Consolidation\\Log\\ConsoleLogLevel' => $vendorDir . '/consolidation/log/src/ConsoleLogLevel.php',
'Consolidation\\Log\\LogOutputStyler' => $vendorDir . '/consolidation/log/src/LogOutputStyler.php',
'Consolidation\\Log\\LogOutputStylerInterface' => $vendorDir . '/consolidation/log/src/LogOutputStylerInterface.php',
'Consolidation\\Log\\Logger' => $vendorDir . '/consolidation/log/src/Logger.php',
'Consolidation\\Log\\LoggerManager' => $vendorDir . '/consolidation/log/src/LoggerManager.php',
'Consolidation\\Log\\StylableLoggerInterface' => $vendorDir . '/consolidation/log/src/StylableLoggerInterface.php',
'Consolidation\\Log\\SymfonyLogOutputStyler' => $vendorDir . '/consolidation/log/src/SymfonyLogOutputStyler.php',
'Consolidation\\Log\\UnstyledLogOutputStyler' => $vendorDir . '/consolidation/log/src/UnstyledLogOutputStyler.php',
'Consolidation\\OutputFormatters\\Exception\\AbstractDataFormatException' => $vendorDir . '/consolidation/output-formatters/src/Exception/AbstractDataFormatException.php',
'Consolidation\\OutputFormatters\\Exception\\IncompatibleDataException' => $vendorDir . '/consolidation/output-formatters/src/Exception/IncompatibleDataException.php',
'Consolidation\\OutputFormatters\\Exception\\InvalidFormatException' => $vendorDir . '/consolidation/output-formatters/src/Exception/InvalidFormatException.php',
'Consolidation\\OutputFormatters\\Exception\\UnknownFieldException' => $vendorDir . '/consolidation/output-formatters/src/Exception/UnknownFieldException.php',
'Consolidation\\OutputFormatters\\Exception\\UnknownFormatException' => $vendorDir . '/consolidation/output-formatters/src/Exception/UnknownFormatException.php',
'Consolidation\\OutputFormatters\\FormatterManager' => $vendorDir . '/consolidation/output-formatters/src/FormatterManager.php',
'Consolidation\\OutputFormatters\\Formatters\\CsvFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/CsvFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\FormatterAwareInterface' => $vendorDir . '/consolidation/output-formatters/src/Formatters/FormatterAwareInterface.php',
'Consolidation\\OutputFormatters\\Formatters\\FormatterAwareTrait' => $vendorDir . '/consolidation/output-formatters/src/Formatters/FormatterAwareTrait.php',
'Consolidation\\OutputFormatters\\Formatters\\FormatterInterface' => $vendorDir . '/consolidation/output-formatters/src/Formatters/FormatterInterface.php',
'Consolidation\\OutputFormatters\\Formatters\\HumanReadableFormat' => $vendorDir . '/consolidation/output-formatters/src/Formatters/HumanReadableFormat.php',
'Consolidation\\OutputFormatters\\Formatters\\JsonFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/JsonFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\ListFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/ListFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\MetadataFormatterInterface' => $vendorDir . '/consolidation/output-formatters/src/Formatters/MetadataFormatterInterface.php',
'Consolidation\\OutputFormatters\\Formatters\\MetadataFormatterTrait' => $vendorDir . '/consolidation/output-formatters/src/Formatters/MetadataFormatterTrait.php',
'Consolidation\\OutputFormatters\\Formatters\\NoOutputFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/NoOutputFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\PrintRFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/PrintRFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\RenderDataInterface' => $vendorDir . '/consolidation/output-formatters/src/Formatters/RenderDataInterface.php',
'Consolidation\\OutputFormatters\\Formatters\\RenderTableDataTrait' => $vendorDir . '/consolidation/output-formatters/src/Formatters/RenderTableDataTrait.php',
'Consolidation\\OutputFormatters\\Formatters\\SectionsFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/SectionsFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\SerializeFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/SerializeFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\StringFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/StringFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\TableFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/TableFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\TsvFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/TsvFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\VarDumpFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/VarDumpFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\VarExportFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/VarExportFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\XmlFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/XmlFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\YamlFormatter' => $vendorDir . '/consolidation/output-formatters/src/Formatters/YamlFormatter.php',
'Consolidation\\OutputFormatters\\Options\\FormatterOptions' => $vendorDir . '/consolidation/output-formatters/src/Options/FormatterOptions.php',
'Consolidation\\OutputFormatters\\Options\\OverrideOptionsInterface' => $vendorDir . '/consolidation/output-formatters/src/Options/OverrideOptionsInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\AbstractListData' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/AbstractListData.php',
'Consolidation\\OutputFormatters\\StructuredData\\AbstractStructuredList' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/AbstractStructuredList.php',
'Consolidation\\OutputFormatters\\StructuredData\\AssociativeList' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/AssociativeList.php',
'Consolidation\\OutputFormatters\\StructuredData\\CallableRenderer' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/CallableRenderer.php',
'Consolidation\\OutputFormatters\\StructuredData\\ConversionInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/ConversionInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\FieldProcessor' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/FieldProcessor.php',
'Consolidation\\OutputFormatters\\StructuredData\\HelpDocument' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/HelpDocument.php',
'Consolidation\\OutputFormatters\\StructuredData\\ListDataFromKeys' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/ListDataFromKeys.php',
'Consolidation\\OutputFormatters\\StructuredData\\ListDataInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/ListDataInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\MetadataHolderInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/MetadataHolderInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\MetadataHolderTrait' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/MetadataHolderTrait.php',
'Consolidation\\OutputFormatters\\StructuredData\\MetadataInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/MetadataInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\NumericCellRenderer' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/NumericCellRenderer.php',
'Consolidation\\OutputFormatters\\StructuredData\\OriginalDataInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/OriginalDataInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\PropertyList' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/PropertyList.php',
'Consolidation\\OutputFormatters\\StructuredData\\RenderCellCollectionInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/RenderCellCollectionInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\RenderCellCollectionTrait' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/RenderCellCollectionTrait.php',
'Consolidation\\OutputFormatters\\StructuredData\\RenderCellInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/RenderCellInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\RestructureInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/RestructureInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\RowsOfFields' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/RowsOfFields.php',
'Consolidation\\OutputFormatters\\StructuredData\\RowsOfFieldsWithMetadata' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/RowsOfFieldsWithMetadata.php',
'Consolidation\\OutputFormatters\\StructuredData\\TableDataInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/TableDataInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\UnstructuredData' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/UnstructuredData.php',
'Consolidation\\OutputFormatters\\StructuredData\\UnstructuredInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/UnstructuredInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\UnstructuredListData' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/UnstructuredListData.php',
'Consolidation\\OutputFormatters\\StructuredData\\Xml\\DomDataInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/Xml/DomDataInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\Xml\\XmlSchema' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/Xml/XmlSchema.php',
'Consolidation\\OutputFormatters\\StructuredData\\Xml\\XmlSchemaInterface' => $vendorDir . '/consolidation/output-formatters/src/StructuredData/Xml/XmlSchemaInterface.php',
'Consolidation\\OutputFormatters\\Transformations\\DomToArraySimplifier' => $vendorDir . '/consolidation/output-formatters/src/Transformations/DomToArraySimplifier.php',
'Consolidation\\OutputFormatters\\Transformations\\OverrideRestructureInterface' => $vendorDir . '/consolidation/output-formatters/src/Transformations/OverrideRestructureInterface.php',
'Consolidation\\OutputFormatters\\Transformations\\PropertyListTableTransformation' => $vendorDir . '/consolidation/output-formatters/src/Transformations/PropertyListTableTransformation.php',
'Consolidation\\OutputFormatters\\Transformations\\PropertyParser' => $vendorDir . '/consolidation/output-formatters/src/Transformations/PropertyParser.php',
'Consolidation\\OutputFormatters\\Transformations\\ReorderFields' => $vendorDir . '/consolidation/output-formatters/src/Transformations/ReorderFields.php',
'Consolidation\\OutputFormatters\\Transformations\\SimplifyToArrayInterface' => $vendorDir . '/consolidation/output-formatters/src/Transformations/SimplifyToArrayInterface.php',
'Consolidation\\OutputFormatters\\Transformations\\StringTransformationInterface' => $vendorDir . '/consolidation/output-formatters/src/Transformations/StringTransformationInterface.php',
'Consolidation\\OutputFormatters\\Transformations\\TableTransformation' => $vendorDir . '/consolidation/output-formatters/src/Transformations/TableTransformation.php',
'Consolidation\\OutputFormatters\\Transformations\\UnstructuredDataFieldAccessor' => $vendorDir . '/consolidation/output-formatters/src/Transformations/UnstructuredDataFieldAccessor.php',
'Consolidation\\OutputFormatters\\Transformations\\UnstructuredDataListTransformation' => $vendorDir . '/consolidation/output-formatters/src/Transformations/UnstructuredDataListTransformation.php',
'Consolidation\\OutputFormatters\\Transformations\\UnstructuredDataTransformation' => $vendorDir . '/consolidation/output-formatters/src/Transformations/UnstructuredDataTransformation.php',
'Consolidation\\OutputFormatters\\Transformations\\WordWrapper' => $vendorDir . '/consolidation/output-formatters/src/Transformations/WordWrapper.php',
'Consolidation\\OutputFormatters\\Transformations\\Wrap\\CalculateWidths' => $vendorDir . '/consolidation/output-formatters/src/Transformations/Wrap/CalculateWidths.php',
'Consolidation\\OutputFormatters\\Transformations\\Wrap\\ColumnWidths' => $vendorDir . '/consolidation/output-formatters/src/Transformations/Wrap/ColumnWidths.php',
'Consolidation\\OutputFormatters\\Validate\\ValidDataTypesInterface' => $vendorDir . '/consolidation/output-formatters/src/Validate/ValidDataTypesInterface.php',
'Consolidation\\OutputFormatters\\Validate\\ValidDataTypesTrait' => $vendorDir . '/consolidation/output-formatters/src/Validate/ValidDataTypesTrait.php',
'Consolidation\\OutputFormatters\\Validate\\ValidationInterface' => $vendorDir . '/consolidation/output-formatters/src/Validate/ValidationInterface.php',
'CssAtCharsetParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtCharsetToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtFontFaceDeclarationToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtFontFaceEndToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtFontFaceParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtFontFaceStartToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtImportParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtImportToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtKeyframesEndToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtKeyframesParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtKeyframesRulesetDeclarationToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtKeyframesRulesetEndToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtKeyframesRulesetStartToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtKeyframesStartToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtMediaEndToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtMediaParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtMediaStartToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtPageDeclarationToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtPageEndToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtPageParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtPageStartToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtVariablesDeclarationToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtVariablesEndToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtVariablesParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssAtVariablesStartToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssCommentParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssCommentToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssCompressColorValuesMinifierPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssCompressExpressionValuesMinifierPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssCompressUnitValuesMinifierPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssConvertFontWeightMinifierPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssConvertHslColorsMinifierPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssConvertLevel3AtKeyframesMinifierFilter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssConvertLevel3PropertiesMinifierFilter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssConvertNamedColorsMinifierPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssConvertRgbColorsMinifierPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssError' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssExpressionParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssImportImportsMinifierFilter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssMin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssMinifier' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssNullToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssOtbsFormatter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssParser' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssRemoveCommentsMinifierFilter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssRemoveEmptyAtBlocksMinifierFilter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssRemoveEmptyRulesetsMinifierFilter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssRemoveLastDelarationSemiColonMinifierFilter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssRulesetDeclarationToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssRulesetEndToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssRulesetParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssRulesetStartToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssSortRulesetPropertiesMinifierFilter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssStringParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssUrlParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssVariablesMinifierFilter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssVariablesMinifierPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'CssWhitesmithsFormatter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'Dflydev\\DotAccessData\\Data' => $vendorDir . '/dflydev/dot-access-data/src/Dflydev/DotAccessData/Data.php',
'Dflydev\\DotAccessData\\DataInterface' => $vendorDir . '/dflydev/dot-access-data/src/Dflydev/DotAccessData/DataInterface.php',
'Dflydev\\DotAccessData\\Util' => $vendorDir . '/dflydev/dot-access-data/src/Dflydev/DotAccessData/Util.php',
'Grasmash\\Expander\\Expander' => $vendorDir . '/grasmash/expander/src/Expander.php',
'Grasmash\\Expander\\Stringifier' => $vendorDir . '/grasmash/expander/src/Stringifier.php',
'Grasmash\\Expander\\StringifierInterface' => $vendorDir . '/grasmash/expander/src/StringifierInterface.php',
'Grasmash\\YamlExpander\\Expander' => $vendorDir . '/grasmash/yaml-expander/src/Expander.php',
'Interop\\Container\\ContainerInterface' => $vendorDir . '/container-interop/container-interop/src/Interop/Container/ContainerInterface.php',
'Interop\\Container\\Exception\\ContainerException' => $vendorDir . '/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.php',
'Interop\\Container\\Exception\\NotFoundException' => $vendorDir . '/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.php',
'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
'League\\Container\\Argument\\ArgumentResolverInterface' => $vendorDir . '/league/container/src/Argument/ArgumentResolverInterface.php',
'League\\Container\\Argument\\ArgumentResolverTrait' => $vendorDir . '/league/container/src/Argument/ArgumentResolverTrait.php',
'League\\Container\\Argument\\RawArgument' => $vendorDir . '/league/container/src/Argument/RawArgument.php',
'League\\Container\\Argument\\RawArgumentInterface' => $vendorDir . '/league/container/src/Argument/RawArgumentInterface.php',
'League\\Container\\Container' => $vendorDir . '/league/container/src/Container.php',
'League\\Container\\ContainerAwareInterface' => $vendorDir . '/league/container/src/ContainerAwareInterface.php',
'League\\Container\\ContainerAwareTrait' => $vendorDir . '/league/container/src/ContainerAwareTrait.php',
'League\\Container\\ContainerInterface' => $vendorDir . '/league/container/src/ContainerInterface.php',
'League\\Container\\Definition\\AbstractDefinition' => $vendorDir . '/league/container/src/Definition/AbstractDefinition.php',
'League\\Container\\Definition\\CallableDefinition' => $vendorDir . '/league/container/src/Definition/CallableDefinition.php',
'League\\Container\\Definition\\ClassDefinition' => $vendorDir . '/league/container/src/Definition/ClassDefinition.php',
'League\\Container\\Definition\\ClassDefinitionInterface' => $vendorDir . '/league/container/src/Definition/ClassDefinitionInterface.php',
'League\\Container\\Definition\\DefinitionFactory' => $vendorDir . '/league/container/src/Definition/DefinitionFactory.php',
'League\\Container\\Definition\\DefinitionFactoryInterface' => $vendorDir . '/league/container/src/Definition/DefinitionFactoryInterface.php',
'League\\Container\\Definition\\DefinitionInterface' => $vendorDir . '/league/container/src/Definition/DefinitionInterface.php',
'League\\Container\\Exception\\NotFoundException' => $vendorDir . '/league/container/src/Exception/NotFoundException.php',
'League\\Container\\ImmutableContainerAwareInterface' => $vendorDir . '/league/container/src/ImmutableContainerAwareInterface.php',
'League\\Container\\ImmutableContainerAwareTrait' => $vendorDir . '/league/container/src/ImmutableContainerAwareTrait.php',
'League\\Container\\ImmutableContainerInterface' => $vendorDir . '/league/container/src/ImmutableContainerInterface.php',
'League\\Container\\Inflector\\Inflector' => $vendorDir . '/league/container/src/Inflector/Inflector.php',
'League\\Container\\Inflector\\InflectorAggregate' => $vendorDir . '/league/container/src/Inflector/InflectorAggregate.php',
'League\\Container\\Inflector\\InflectorAggregateInterface' => $vendorDir . '/league/container/src/Inflector/InflectorAggregateInterface.php',
'League\\Container\\ReflectionContainer' => $vendorDir . '/league/container/src/ReflectionContainer.php',
'League\\Container\\ServiceProvider\\AbstractServiceProvider' => $vendorDir . '/league/container/src/ServiceProvider/AbstractServiceProvider.php',
'League\\Container\\ServiceProvider\\AbstractSignatureServiceProvider' => $vendorDir . '/league/container/src/ServiceProvider/AbstractSignatureServiceProvider.php',
'League\\Container\\ServiceProvider\\BootableServiceProviderInterface' => $vendorDir . '/league/container/src/ServiceProvider/BootableServiceProviderInterface.php',
'League\\Container\\ServiceProvider\\ServiceProviderAggregate' => $vendorDir . '/league/container/src/ServiceProvider/ServiceProviderAggregate.php',
'League\\Container\\ServiceProvider\\ServiceProviderAggregateInterface' => $vendorDir . '/league/container/src/ServiceProvider/ServiceProviderAggregateInterface.php',
'League\\Container\\ServiceProvider\\ServiceProviderInterface' => $vendorDir . '/league/container/src/ServiceProvider/ServiceProviderInterface.php',
'League\\Container\\ServiceProvider\\SignatureServiceProviderInterface' => $vendorDir . '/league/container/src/ServiceProvider/SignatureServiceProviderInterface.php',
'OS_Guess' => $vendorDir . '/pear/pear-core-minimal/src/OS/Guess.php',
'PEAR' => $vendorDir . '/pear/pear-core-minimal/src/PEAR.php',
'PEAR_Error' => $vendorDir . '/pear/pear-core-minimal/src/PEAR.php',
'PEAR_ErrorStack' => $vendorDir . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php',
'PEAR_Exception' => $vendorDir . '/pear/pear_exception/PEAR/Exception.php',
'Patchwork\\JSqueeze' => $vendorDir . '/patchwork/jsqueeze/src/JSqueeze.php',
'Psr\\Container\\ContainerExceptionInterface' => $vendorDir . '/psr/container/src/ContainerExceptionInterface.php',
'Psr\\Container\\ContainerInterface' => $vendorDir . '/psr/container/src/ContainerInterface.php',
'Psr\\Container\\NotFoundExceptionInterface' => $vendorDir . '/psr/container/src/NotFoundExceptionInterface.php',
'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php',
'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php',
'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php',
'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php',
'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php',
'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php',
'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php',
'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php',
'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
'Psr\\Log\\Test\\TestLogger' => $vendorDir . '/psr/log/Psr/Log/Test/TestLogger.php',
'Robo\\Application' => $baseDir . '/src/Application.php',
'Robo\\ClassDiscovery\\AbstractClassDiscovery' => $baseDir . '/src/ClassDiscovery/AbstractClassDiscovery.php',
'Robo\\ClassDiscovery\\ClassDiscoveryInterface' => $baseDir . '/src/ClassDiscovery/ClassDiscoveryInterface.php',
'Robo\\ClassDiscovery\\RelativeNamespaceDiscovery' => $baseDir . '/src/ClassDiscovery/RelativeNamespaceDiscovery.php',
'Robo\\Collection\\CallableTask' => $baseDir . '/src/Collection/CallableTask.php',
'Robo\\Collection\\Collection' => $baseDir . '/src/Collection/Collection.php',
'Robo\\Collection\\CollectionBuilder' => $baseDir . '/src/Collection/CollectionBuilder.php',
'Robo\\Collection\\CollectionInterface' => $baseDir . '/src/Collection/CollectionInterface.php',
'Robo\\Collection\\CollectionProcessHook' => $baseDir . '/src/Collection/CollectionProcessHook.php',
'Robo\\Collection\\CompletionWrapper' => $baseDir . '/src/Collection/CompletionWrapper.php',
'Robo\\Collection\\Element' => $baseDir . '/src/Collection/Element.php',
'Robo\\Collection\\NestedCollectionInterface' => $baseDir . '/src/Collection/NestedCollectionInterface.php',
'Robo\\Collection\\TaskForEach' => $baseDir . '/src/Collection/TaskForEach.php',
'Robo\\Collection\\Temporary' => $baseDir . '/src/Collection/Temporary.php',
'Robo\\Collection\\loadTasks' => $baseDir . '/src/Collection/loadTasks.php',
'Robo\\Common\\BuilderAwareTrait' => $baseDir . '/src/Common/BuilderAwareTrait.php',
'Robo\\Common\\CommandArguments' => $baseDir . '/src/Common/CommandArguments.php',
'Robo\\Common\\CommandReceiver' => $baseDir . '/src/Common/CommandReceiver.php',
'Robo\\Common\\ConfigAwareTrait' => $baseDir . '/src/Common/ConfigAwareTrait.php',
'Robo\\Common\\DynamicParams' => $baseDir . '/src/Common/DynamicParams.php',
'Robo\\Common\\ExecCommand' => $baseDir . '/src/Common/ExecCommand.php',
'Robo\\Common\\ExecOneCommand' => $baseDir . '/src/Common/ExecOneCommand.php',
'Robo\\Common\\ExecTrait' => $baseDir . '/src/Common/ExecTrait.php',
'Robo\\Common\\IO' => $baseDir . '/src/Common/IO.php',
'Robo\\Common\\InflectionTrait' => $baseDir . '/src/Common/InflectionTrait.php',
'Robo\\Common\\InputAwareTrait' => $baseDir . '/src/Common/InputAwareTrait.php',
'Robo\\Common\\OutputAdapter' => $baseDir . '/src/Common/OutputAdapter.php',
'Robo\\Common\\OutputAwareTrait' => $baseDir . '/src/Common/OutputAwareTrait.php',
'Robo\\Common\\ProcessExecutor' => $baseDir . '/src/Common/ProcessExecutor.php',
'Robo\\Common\\ProcessUtils' => $baseDir . '/src/Common/ProcessUtils.php',
'Robo\\Common\\ProgressIndicator' => $baseDir . '/src/Common/ProgressIndicator.php',
'Robo\\Common\\ProgressIndicatorAwareTrait' => $baseDir . '/src/Common/ProgressIndicatorAwareTrait.php',
'Robo\\Common\\ResourceExistenceChecker' => $baseDir . '/src/Common/ResourceExistenceChecker.php',
'Robo\\Common\\TaskIO' => $baseDir . '/src/Common/TaskIO.php',
'Robo\\Common\\TimeKeeper' => $baseDir . '/src/Common/TimeKeeper.php',
'Robo\\Common\\Timer' => $baseDir . '/src/Common/Timer.php',
'Robo\\Common\\VerbosityThresholdTrait' => $baseDir . '/src/Common/VerbosityThresholdTrait.php',
'Robo\\Config' => $baseDir . '/src/Config.php',
'Robo\\Config\\Config' => $baseDir . '/src/Config/Config.php',
'Robo\\Config\\GlobalOptionDefaultValuesInterface' => $baseDir . '/src/Config/GlobalOptionDefaultValuesInterface.php',
'Robo\\Contract\\BuilderAwareInterface' => $baseDir . '/src/Contract/BuilderAwareInterface.php',
'Robo\\Contract\\CommandInterface' => $baseDir . '/src/Contract/CommandInterface.php',
'Robo\\Contract\\CompletionInterface' => $baseDir . '/src/Contract/CompletionInterface.php',
'Robo\\Contract\\ConfigAwareInterface' => $baseDir . '/src/Contract/ConfigAwareInterface.php',
'Robo\\Contract\\IOAwareInterface' => $baseDir . '/src/Contract/IOAwareInterface.php',
'Robo\\Contract\\InflectionInterface' => $baseDir . '/src/Contract/InflectionInterface.php',
'Robo\\Contract\\OutputAdapterInterface' => $baseDir . '/src/Contract/OutputAdapterInterface.php',
'Robo\\Contract\\OutputAwareInterface' => $baseDir . '/src/Contract/OutputAwareInterface.php',
'Robo\\Contract\\PrintedInterface' => $baseDir . '/src/Contract/PrintedInterface.php',
'Robo\\Contract\\ProgressIndicatorAwareInterface' => $baseDir . '/src/Contract/ProgressIndicatorAwareInterface.php',
'Robo\\Contract\\ProgressInterface' => $baseDir . '/src/Contract/ProgressInterface.php',
'Robo\\Contract\\RollbackInterface' => $baseDir . '/src/Contract/RollbackInterface.php',
'Robo\\Contract\\SimulatedInterface' => $baseDir . '/src/Contract/SimulatedInterface.php',
'Robo\\Contract\\TaskInterface' => $baseDir . '/src/Contract/TaskInterface.php',
'Robo\\Contract\\VerbosityThresholdInterface' => $baseDir . '/src/Contract/VerbosityThresholdInterface.php',
'Robo\\Contract\\WrappedTaskInterface' => $baseDir . '/src/Contract/WrappedTaskInterface.php',
'Robo\\Exception\\AbortTasksException' => $baseDir . '/src/Exception/AbortTasksException.php',
'Robo\\Exception\\TaskException' => $baseDir . '/src/Exception/TaskException.php',
'Robo\\Exception\\TaskExitException' => $baseDir . '/src/Exception/TaskExitException.php',
'Robo\\GlobalOptionsEventListener' => $baseDir . '/src/GlobalOptionsEventListener.php',
'Robo\\LoadAllTasks' => $baseDir . '/src/LoadAllTasks.php',
'Robo\\Log\\ResultPrinter' => $baseDir . '/src/Log/ResultPrinter.php',
'Robo\\Log\\RoboLogLevel' => $baseDir . '/src/Log/RoboLogLevel.php',
'Robo\\Log\\RoboLogStyle' => $baseDir . '/src/Log/RoboLogStyle.php',
'Robo\\Log\\RoboLogger' => $baseDir . '/src/Log/RoboLogger.php',
'Robo\\Result' => $baseDir . '/src/Result.php',
'Robo\\ResultData' => $baseDir . '/src/ResultData.php',
'Robo\\Robo' => $baseDir . '/src/Robo.php',
'Robo\\Runner' => $baseDir . '/src/Runner.php',
'Robo\\State\\Consumer' => $baseDir . '/src/State/Consumer.php',
'Robo\\State\\Data' => $baseDir . '/src/State/Data.php',
'Robo\\State\\StateAwareInterface' => $baseDir . '/src/State/StateAwareInterface.php',
'Robo\\State\\StateAwareTrait' => $baseDir . '/src/State/StateAwareTrait.php',
'Robo\\Symfony\\SymfonyStyleInjector' => $baseDir . '/src/Symfony/SymfonyStyleInjector.php',
'Robo\\TaskAccessor' => $baseDir . '/src/TaskAccessor.php',
'Robo\\TaskInfo' => $baseDir . '/src/TaskInfo.php',
'Robo\\Task\\ApiGen\\ApiGen' => $baseDir . '/src/Task/ApiGen/ApiGen.php',
'Robo\\Task\\ApiGen\\loadTasks' => $baseDir . '/src/Task/ApiGen/loadTasks.php',
'Robo\\Task\\Archive\\Extract' => $baseDir . '/src/Task/Archive/Extract.php',
'Robo\\Task\\Archive\\Pack' => $baseDir . '/src/Task/Archive/Pack.php',
'Robo\\Task\\Archive\\loadTasks' => $baseDir . '/src/Task/Archive/loadTasks.php',
'Robo\\Task\\Assets\\CssPreprocessor' => $baseDir . '/src/Task/Assets/CssPreprocessor.php',
'Robo\\Task\\Assets\\ImageMinify' => $baseDir . '/src/Task/Assets/ImageMinify.php',
'Robo\\Task\\Assets\\Less' => $baseDir . '/src/Task/Assets/Less.php',
'Robo\\Task\\Assets\\Minify' => $baseDir . '/src/Task/Assets/Minify.php',
'Robo\\Task\\Assets\\Scss' => $baseDir . '/src/Task/Assets/Scss.php',
'Robo\\Task\\Assets\\loadTasks' => $baseDir . '/src/Task/Assets/loadTasks.php',
'Robo\\Task\\BaseTask' => $baseDir . '/src/Task/BaseTask.php',
'Robo\\Task\\Base\\Exec' => $baseDir . '/src/Task/Base/Exec.php',
'Robo\\Task\\Base\\ExecStack' => $baseDir . '/src/Task/Base/ExecStack.php',
'Robo\\Task\\Base\\ParallelExec' => $baseDir . '/src/Task/Base/ParallelExec.php',
'Robo\\Task\\Base\\SymfonyCommand' => $baseDir . '/src/Task/Base/SymfonyCommand.php',
'Robo\\Task\\Base\\Watch' => $baseDir . '/src/Task/Base/Watch.php',
'Robo\\Task\\Base\\loadShortcuts' => $baseDir . '/src/Task/Base/loadShortcuts.php',
'Robo\\Task\\Base\\loadTasks' => $baseDir . '/src/Task/Base/loadTasks.php',
'Robo\\Task\\Bower\\Base' => $baseDir . '/src/Task/Bower/Base.php',
'Robo\\Task\\Bower\\Install' => $baseDir . '/src/Task/Bower/Install.php',
'Robo\\Task\\Bower\\Update' => $baseDir . '/src/Task/Bower/Update.php',
'Robo\\Task\\Bower\\loadTasks' => $baseDir . '/src/Task/Bower/loadTasks.php',
'Robo\\Task\\CommandStack' => $baseDir . '/src/Task/CommandStack.php',
'Robo\\Task\\Composer\\Base' => $baseDir . '/src/Task/Composer/Base.php',
'Robo\\Task\\Composer\\Config' => $baseDir . '/src/Task/Composer/Config.php',
'Robo\\Task\\Composer\\CreateProject' => $baseDir . '/src/Task/Composer/CreateProject.php',
'Robo\\Task\\Composer\\DumpAutoload' => $baseDir . '/src/Task/Composer/DumpAutoload.php',
'Robo\\Task\\Composer\\Init' => $baseDir . '/src/Task/Composer/Init.php',
'Robo\\Task\\Composer\\Install' => $baseDir . '/src/Task/Composer/Install.php',
'Robo\\Task\\Composer\\Remove' => $baseDir . '/src/Task/Composer/Remove.php',
'Robo\\Task\\Composer\\RequireDependency' => $baseDir . '/src/Task/Composer/RequireDependency.php',
'Robo\\Task\\Composer\\Update' => $baseDir . '/src/Task/Composer/Update.php',
'Robo\\Task\\Composer\\Validate' => $baseDir . '/src/Task/Composer/Validate.php',
'Robo\\Task\\Composer\\loadTasks' => $baseDir . '/src/Task/Composer/loadTasks.php',
'Robo\\Task\\Development\\Changelog' => $baseDir . '/src/Task/Development/Changelog.php',
'Robo\\Task\\Development\\GenerateMarkdownDoc' => $baseDir . '/src/Task/Development/GenerateMarkdownDoc.php',
'Robo\\Task\\Development\\GenerateTask' => $baseDir . '/src/Task/Development/GenerateTask.php',
'Robo\\Task\\Development\\GitHub' => $baseDir . '/src/Task/Development/GitHub.php',
'Robo\\Task\\Development\\GitHubRelease' => $baseDir . '/src/Task/Development/GitHubRelease.php',
'Robo\\Task\\Development\\OpenBrowser' => $baseDir . '/src/Task/Development/OpenBrowser.php',
'Robo\\Task\\Development\\PackPhar' => $baseDir . '/src/Task/Development/PackPhar.php',
'Robo\\Task\\Development\\PhpServer' => $baseDir . '/src/Task/Development/PhpServer.php',
'Robo\\Task\\Development\\SemVer' => $baseDir . '/src/Task/Development/SemVer.php',
'Robo\\Task\\Development\\loadTasks' => $baseDir . '/src/Task/Development/loadTasks.php',
'Robo\\Task\\Docker\\Base' => $baseDir . '/src/Task/Docker/Base.php',
'Robo\\Task\\Docker\\Build' => $baseDir . '/src/Task/Docker/Build.php',
'Robo\\Task\\Docker\\Commit' => $baseDir . '/src/Task/Docker/Commit.php',
'Robo\\Task\\Docker\\Exec' => $baseDir . '/src/Task/Docker/Exec.php',
'Robo\\Task\\Docker\\Pull' => $baseDir . '/src/Task/Docker/Pull.php',
'Robo\\Task\\Docker\\Remove' => $baseDir . '/src/Task/Docker/Remove.php',
'Robo\\Task\\Docker\\Result' => $baseDir . '/src/Task/Docker/Result.php',
'Robo\\Task\\Docker\\Run' => $baseDir . '/src/Task/Docker/Run.php',
'Robo\\Task\\Docker\\Start' => $baseDir . '/src/Task/Docker/Start.php',
'Robo\\Task\\Docker\\Stop' => $baseDir . '/src/Task/Docker/Stop.php',
'Robo\\Task\\Docker\\loadTasks' => $baseDir . '/src/Task/Docker/loadTasks.php',
'Robo\\Task\\File\\Concat' => $baseDir . '/src/Task/File/Concat.php',
'Robo\\Task\\File\\Replace' => $baseDir . '/src/Task/File/Replace.php',
'Robo\\Task\\File\\TmpFile' => $baseDir . '/src/Task/File/TmpFile.php',
'Robo\\Task\\File\\Write' => $baseDir . '/src/Task/File/Write.php',
'Robo\\Task\\File\\loadTasks' => $baseDir . '/src/Task/File/loadTasks.php',
'Robo\\Task\\Filesystem\\BaseDir' => $baseDir . '/src/Task/Filesystem/BaseDir.php',
'Robo\\Task\\Filesystem\\CleanDir' => $baseDir . '/src/Task/Filesystem/CleanDir.php',
'Robo\\Task\\Filesystem\\CopyDir' => $baseDir . '/src/Task/Filesystem/CopyDir.php',
'Robo\\Task\\Filesystem\\DeleteDir' => $baseDir . '/src/Task/Filesystem/DeleteDir.php',
'Robo\\Task\\Filesystem\\FilesystemStack' => $baseDir . '/src/Task/Filesystem/FilesystemStack.php',
'Robo\\Task\\Filesystem\\FlattenDir' => $baseDir . '/src/Task/Filesystem/FlattenDir.php',
'Robo\\Task\\Filesystem\\MirrorDir' => $baseDir . '/src/Task/Filesystem/MirrorDir.php',
'Robo\\Task\\Filesystem\\TmpDir' => $baseDir . '/src/Task/Filesystem/TmpDir.php',
'Robo\\Task\\Filesystem\\WorkDir' => $baseDir . '/src/Task/Filesystem/WorkDir.php',
'Robo\\Task\\Filesystem\\loadShortcuts' => $baseDir . '/src/Task/Filesystem/loadShortcuts.php',
'Robo\\Task\\Filesystem\\loadTasks' => $baseDir . '/src/Task/Filesystem/loadTasks.php',
'Robo\\Task\\Gulp\\Base' => $baseDir . '/src/Task/Gulp/Base.php',
'Robo\\Task\\Gulp\\Run' => $baseDir . '/src/Task/Gulp/Run.php',
'Robo\\Task\\Gulp\\loadTasks' => $baseDir . '/src/Task/Gulp/loadTasks.php',
'Robo\\Task\\Npm\\Base' => $baseDir . '/src/Task/Npm/Base.php',
'Robo\\Task\\Npm\\Install' => $baseDir . '/src/Task/Npm/Install.php',
'Robo\\Task\\Npm\\Update' => $baseDir . '/src/Task/Npm/Update.php',
'Robo\\Task\\Npm\\loadTasks' => $baseDir . '/src/Task/Npm/loadTasks.php',
'Robo\\Task\\Remote\\Rsync' => $baseDir . '/src/Task/Remote/Rsync.php',
'Robo\\Task\\Remote\\Ssh' => $baseDir . '/src/Task/Remote/Ssh.php',
'Robo\\Task\\Remote\\loadTasks' => $baseDir . '/src/Task/Remote/loadTasks.php',
'Robo\\Task\\Simulator' => $baseDir . '/src/Task/Simulator.php',
'Robo\\Task\\StackBasedTask' => $baseDir . '/src/Task/StackBasedTask.php',
'Robo\\Task\\Testing\\Atoum' => $baseDir . '/src/Task/Testing/Atoum.php',
'Robo\\Task\\Testing\\Behat' => $baseDir . '/src/Task/Testing/Behat.php',
'Robo\\Task\\Testing\\Codecept' => $baseDir . '/src/Task/Testing/Codecept.php',
'Robo\\Task\\Testing\\PHPUnit' => $baseDir . '/src/Task/Testing/PHPUnit.php',
'Robo\\Task\\Testing\\Phpspec' => $baseDir . '/src/Task/Testing/Phpspec.php',
'Robo\\Task\\Testing\\loadTasks' => $baseDir . '/src/Task/Testing/loadTasks.php',
'Robo\\Task\\Vcs\\GitStack' => $baseDir . '/src/Task/Vcs/GitStack.php',
'Robo\\Task\\Vcs\\HgStack' => $baseDir . '/src/Task/Vcs/HgStack.php',
'Robo\\Task\\Vcs\\SvnStack' => $baseDir . '/src/Task/Vcs/SvnStack.php',
'Robo\\Task\\Vcs\\loadShortcuts' => $baseDir . '/src/Task/Vcs/loadShortcuts.php',
'Robo\\Task\\Vcs\\loadTasks' => $baseDir . '/src/Task/Vcs/loadTasks.php',
'Robo\\Tasks' => $baseDir . '/src/Tasks.php',
'SelfUpdate\\SelfUpdateCommand' => $vendorDir . '/consolidation/self-update/src/SelfUpdateCommand.php',
'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php',
'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => $vendorDir . '/symfony/console/CommandLoader/CommandLoaderInterface.php',
'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/ContainerCommandLoader.php',
'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/FactoryCommandLoader.php',
'Symfony\\Component\\Console\\Command\\Command' => $vendorDir . '/symfony/console/Command/Command.php',
'Symfony\\Component\\Console\\Command\\HelpCommand' => $vendorDir . '/symfony/console/Command/HelpCommand.php',
'Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Command/ListCommand.php',
'Symfony\\Component\\Console\\Command\\LockableTrait' => $vendorDir . '/symfony/console/Command/LockableTrait.php',
'Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.php',
'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => $vendorDir . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php',
'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Descriptor/ApplicationDescription.php',
'Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Descriptor/Descriptor.php',
'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php',
'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php',
'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php',
'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php',
'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php',
'Symfony\\Component\\Console\\EventListener\\ErrorListener' => $vendorDir . '/symfony/console/EventListener/ErrorListener.php',
'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Event/ConsoleCommandEvent.php',
'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => $vendorDir . '/symfony/console/Event/ConsoleErrorEvent.php',
'Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Event/ConsoleEvent.php',
'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => $vendorDir . '/symfony/console/Event/ConsoleTerminateEvent.php',
'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => $vendorDir . '/symfony/console/Exception/CommandNotFoundException.php',
'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/console/Exception/ExceptionInterface.php',
'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/console/Exception/InvalidArgumentException.php',
'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => $vendorDir . '/symfony/console/Exception/InvalidOptionException.php',
'Symfony\\Component\\Console\\Exception\\LogicException' => $vendorDir . '/symfony/console/Exception/LogicException.php',
'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => $vendorDir . '/symfony/console/Exception/NamespaceNotFoundException.php',
'Symfony\\Component\\Console\\Exception\\RuntimeException' => $vendorDir . '/symfony/console/Exception/RuntimeException.php',
'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => $vendorDir . '/symfony/console/Formatter/OutputFormatter.php',
'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterInterface.php',
'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyle.php',
'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleInterface.php',
'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleStack.php',
'Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php',
'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php',
'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php',
'Symfony\\Component\\Console\\Helper\\Dumper' => $vendorDir . '/symfony/console/Helper/Dumper.php',
'Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Helper/FormatterHelper.php',
'Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Helper/Helper.php',
'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php',
'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php',
'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php',
'Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php',
'Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php',
'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php',
'Symfony\\Component\\Console\\Helper\\QuestionHelper' => $vendorDir . '/symfony/console/Helper/QuestionHelper.php',
'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => $vendorDir . '/symfony/console/Helper/SymfonyQuestionHelper.php',
'Symfony\\Component\\Console\\Helper\\Table' => $vendorDir . '/symfony/console/Helper/Table.php',
'Symfony\\Component\\Console\\Helper\\TableCell' => $vendorDir . '/symfony/console/Helper/TableCell.php',
'Symfony\\Component\\Console\\Helper\\TableRows' => $vendorDir . '/symfony/console/Helper/TableRows.php',
'Symfony\\Component\\Console\\Helper\\TableSeparator' => $vendorDir . '/symfony/console/Helper/TableSeparator.php',
'Symfony\\Component\\Console\\Helper\\TableStyle' => $vendorDir . '/symfony/console/Helper/TableStyle.php',
'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php',
'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php',
'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php',
'Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Input/InputArgument.php',
'Symfony\\Component\\Console\\Input\\InputAwareInterface' => $vendorDir . '/symfony/console/Input/InputAwareInterface.php',
'Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Input/InputDefinition.php',
'Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Input/InputInterface.php',
'Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Input/InputOption.php',
'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => $vendorDir . '/symfony/console/Input/StreamableInputInterface.php',
'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php',
'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php',
'Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php',
'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php',
'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php',
'Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => $vendorDir . '/symfony/console/Output/ConsoleSectionOutput.php',
'Symfony\\Component\\Console\\Output\\NullOutput' => $vendorDir . '/symfony/console/Output/NullOutput.php',
'Symfony\\Component\\Console\\Output\\Output' => $vendorDir . '/symfony/console/Output/Output.php',
'Symfony\\Component\\Console\\Output\\OutputInterface' => $vendorDir . '/symfony/console/Output/OutputInterface.php',
'Symfony\\Component\\Console\\Output\\StreamOutput' => $vendorDir . '/symfony/console/Output/StreamOutput.php',
'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => $vendorDir . '/symfony/console/Question/ChoiceQuestion.php',
'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => $vendorDir . '/symfony/console/Question/ConfirmationQuestion.php',
'Symfony\\Component\\Console\\Question\\Question' => $vendorDir . '/symfony/console/Question/Question.php',
'Symfony\\Component\\Console\\Style\\OutputStyle' => $vendorDir . '/symfony/console/Style/OutputStyle.php',
'Symfony\\Component\\Console\\Style\\StyleInterface' => $vendorDir . '/symfony/console/Style/StyleInterface.php',
'Symfony\\Component\\Console\\Style\\SymfonyStyle' => $vendorDir . '/symfony/console/Style/SymfonyStyle.php',
'Symfony\\Component\\Console\\Terminal' => $vendorDir . '/symfony/console/Terminal.php',
'Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php',
'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php',
'Symfony\\Component\\Console\\Tester\\TesterTrait' => $vendorDir . '/symfony/console/Tester/TesterTrait.php',
'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php',
'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php',
'Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener' => $vendorDir . '/symfony/event-dispatcher/Debug/WrappedListener.php',
'Symfony\\Component\\EventDispatcher\\DependencyInjection\\AddEventAliasesPass' => $vendorDir . '/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php',
'Symfony\\Component\\EventDispatcher\\DependencyInjection\\ExtractingEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php',
'Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass' => $vendorDir . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php',
'Symfony\\Component\\EventDispatcher\\Event' => $vendorDir . '/symfony/event-dispatcher/Event.php',
'Symfony\\Component\\EventDispatcher\\EventDispatcher' => $vendorDir . '/symfony/event-dispatcher/EventDispatcher.php',
'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher/EventDispatcherInterface.php',
'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/symfony/event-dispatcher/EventSubscriberInterface.php',
'Symfony\\Component\\EventDispatcher\\GenericEvent' => $vendorDir . '/symfony/event-dispatcher/GenericEvent.php',
'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/ImmutableEventDispatcher.php',
'Symfony\\Component\\EventDispatcher\\LegacyEventDispatcherProxy' => $vendorDir . '/symfony/event-dispatcher/LegacyEventDispatcherProxy.php',
'Symfony\\Component\\EventDispatcher\\LegacyEventProxy' => $vendorDir . '/symfony/event-dispatcher/LegacyEventProxy.php',
'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php',
'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php',
'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php',
'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/IOExceptionInterface.php',
'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/filesystem/Exception/InvalidArgumentException.php',
'Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Filesystem.php',
'Symfony\\Component\\Finder\\Comparator\\Comparator' => $vendorDir . '/symfony/finder/Comparator/Comparator.php',
'Symfony\\Component\\Finder\\Comparator\\DateComparator' => $vendorDir . '/symfony/finder/Comparator/DateComparator.php',
'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => $vendorDir . '/symfony/finder/Comparator/NumberComparator.php',
'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/finder/Exception/AccessDeniedException.php',
'Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => $vendorDir . '/symfony/finder/Exception/DirectoryNotFoundException.php',
'Symfony\\Component\\Finder\\Finder' => $vendorDir . '/symfony/finder/Finder.php',
'Symfony\\Component\\Finder\\Gitignore' => $vendorDir . '/symfony/finder/Gitignore.php',
'Symfony\\Component\\Finder\\Glob' => $vendorDir . '/symfony/finder/Glob.php',
'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => $vendorDir . '/symfony/finder/Iterator/CustomFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DateRangeFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DepthRangeFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => $vendorDir . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FileTypeFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilecontentFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilenameFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => $vendorDir . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => $vendorDir . '/symfony/finder/Iterator/PathFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => $vendorDir . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php',
'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/SizeRangeFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => $vendorDir . '/symfony/finder/Iterator/SortableIterator.php',
'Symfony\\Component\\Finder\\SplFileInfo' => $vendorDir . '/symfony/finder/SplFileInfo.php',
'Symfony\\Component\\Process\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/process/Exception/ExceptionInterface.php',
'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/process/Exception/InvalidArgumentException.php',
'Symfony\\Component\\Process\\Exception\\LogicException' => $vendorDir . '/symfony/process/Exception/LogicException.php',
'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => $vendorDir . '/symfony/process/Exception/ProcessFailedException.php',
'Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => $vendorDir . '/symfony/process/Exception/ProcessSignaledException.php',
'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => $vendorDir . '/symfony/process/Exception/ProcessTimedOutException.php',
'Symfony\\Component\\Process\\Exception\\RuntimeException' => $vendorDir . '/symfony/process/Exception/RuntimeException.php',
'Symfony\\Component\\Process\\ExecutableFinder' => $vendorDir . '/symfony/process/ExecutableFinder.php',
'Symfony\\Component\\Process\\InputStream' => $vendorDir . '/symfony/process/InputStream.php',
'Symfony\\Component\\Process\\PhpExecutableFinder' => $vendorDir . '/symfony/process/PhpExecutableFinder.php',
'Symfony\\Component\\Process\\PhpProcess' => $vendorDir . '/symfony/process/PhpProcess.php',
'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => $vendorDir . '/symfony/process/Pipes/AbstractPipes.php',
'Symfony\\Component\\Process\\Pipes\\PipesInterface' => $vendorDir . '/symfony/process/Pipes/PipesInterface.php',
'Symfony\\Component\\Process\\Pipes\\UnixPipes' => $vendorDir . '/symfony/process/Pipes/UnixPipes.php',
'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => $vendorDir . '/symfony/process/Pipes/WindowsPipes.php',
'Symfony\\Component\\Process\\Process' => $vendorDir . '/symfony/process/Process.php',
'Symfony\\Component\\Process\\ProcessUtils' => $vendorDir . '/symfony/process/ProcessUtils.php',
'Symfony\\Component\\Yaml\\Command\\LintCommand' => $vendorDir . '/symfony/yaml/Command/LintCommand.php',
'Symfony\\Component\\Yaml\\Dumper' => $vendorDir . '/symfony/yaml/Dumper.php',
'Symfony\\Component\\Yaml\\Escaper' => $vendorDir . '/symfony/yaml/Escaper.php',
'Symfony\\Component\\Yaml\\Exception\\DumpException' => $vendorDir . '/symfony/yaml/Exception/DumpException.php',
'Symfony\\Component\\Yaml\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/yaml/Exception/ExceptionInterface.php',
'Symfony\\Component\\Yaml\\Exception\\ParseException' => $vendorDir . '/symfony/yaml/Exception/ParseException.php',
'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => $vendorDir . '/symfony/yaml/Exception/RuntimeException.php',
'Symfony\\Component\\Yaml\\Inline' => $vendorDir . '/symfony/yaml/Inline.php',
'Symfony\\Component\\Yaml\\Parser' => $vendorDir . '/symfony/yaml/Parser.php',
'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => $vendorDir . '/symfony/yaml/Tag/TaggedValue.php',
'Symfony\\Component\\Yaml\\Unescaper' => $vendorDir . '/symfony/yaml/Unescaper.php',
'Symfony\\Component\\Yaml\\Yaml' => $vendorDir . '/symfony/yaml/Yaml.php',
'Symfony\\Contracts\\EventDispatcher\\Event' => $vendorDir . '/symfony/event-dispatcher-contracts/Event.php',
'Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher-contracts/EventDispatcherInterface.php',
'Symfony\\Contracts\\Service\\ResetInterface' => $vendorDir . '/symfony/service-contracts/ResetInterface.php',
'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => $vendorDir . '/symfony/service-contracts/ServiceLocatorTrait.php',
'Symfony\\Contracts\\Service\\ServiceProviderInterface' => $vendorDir . '/symfony/service-contracts/ServiceProviderInterface.php',
'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberInterface.php',
'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberTrait.php',
'Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => $vendorDir . '/symfony/service-contracts/Test/ServiceLocatorTest.php',
'Symfony\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php',
'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php',
'Symfony\\Polyfill\\Php73\\Php73' => $vendorDir . '/symfony/polyfill-php73/Php73.php',
'System' => $vendorDir . '/pear/pear-core-minimal/src/System.php',
'aCssAtBlockEndToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'aCssAtBlockStartToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'aCssDeclarationToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'aCssFormatter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'aCssMinifierFilter' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'aCssMinifierPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'aCssParserPlugin' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'aCssRulesetEndToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'aCssRulesetStartToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
'aCssToken' => $vendorDir . '/natxet/cssmin/src/CssMin.php',
);
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit1040fe349fd04fbe8683abc5aae8aae1
{
public static $files = array (
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
);
public static $prefixLengthsPsr4 = array (
'S' =>
array (
'Symfony\\Polyfill\\Php73\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Ctype\\' => 23,
'Symfony\\Contracts\\Service\\' => 26,
'Symfony\\Contracts\\EventDispatcher\\' => 34,
'Symfony\\Component\\Yaml\\' => 23,
'Symfony\\Component\\Process\\' => 26,
'Symfony\\Component\\Finder\\' => 25,
'Symfony\\Component\\Filesystem\\' => 29,
'Symfony\\Component\\EventDispatcher\\' => 34,
'Symfony\\Component\\Console\\' => 26,
'SelfUpdate\\' => 11,
),
'R' =>
array (
'Robo\\' => 5,
'RoboExample\\' => 12,
),
'P' =>
array (
'Psr\\Log\\' => 8,
'Psr\\Container\\' => 14,
'Patchwork\\' => 10,
),
'L' =>
array (
'League\\Container\\' => 17,
),
'I' =>
array (
'Interop\\Container\\' => 18,
),
'G' =>
array (
'Grasmash\\YamlExpander\\' => 22,
'Grasmash\\Expander\\' => 18,
),
'C' =>
array (
'Consolidation\\OutputFormatters\\' => 31,
'Consolidation\\Log\\' => 18,
'Consolidation\\Config\\' => 21,
'Consolidation\\AnnotatedCommand\\' => 31,
),
);
public static $prefixDirsPsr4 = array (
'Symfony\\Polyfill\\Php73\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Polyfill\\Ctype\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'Symfony\\Contracts\\Service\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/service-contracts',
),
'Symfony\\Contracts\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts',
),
'Symfony\\Component\\Yaml\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/yaml',
),
'Symfony\\Component\\Process\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/process',
),
'Symfony\\Component\\Finder\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/finder',
),
'Symfony\\Component\\Filesystem\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/filesystem',
),
'Symfony\\Component\\EventDispatcher\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
),
'Symfony\\Component\\Console\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/console',
),
'SelfUpdate\\' =>
array (
0 => __DIR__ . '/..' . '/consolidation/self-update/src',
),
'Robo\\' =>
array (
0 => __DIR__ . '/../..' . '/src',
1 => __DIR__ . '/../..' . '/tests/src',
),
'RoboExample\\' =>
array (
0 => __DIR__ . '/../..' . '/examples/src',
),
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
),
'Psr\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/psr/container/src',
),
'Patchwork\\' =>
array (
0 => __DIR__ . '/..' . '/patchwork/jsqueeze/src',
),
'League\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/league/container/src',
),
'Interop\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container',
),
'Grasmash\\YamlExpander\\' =>
array (
0 => __DIR__ . '/..' . '/grasmash/yaml-expander/src',
),
'Grasmash\\Expander\\' =>
array (
0 => __DIR__ . '/..' . '/grasmash/expander/src',
),
'Consolidation\\OutputFormatters\\' =>
array (
0 => __DIR__ . '/..' . '/consolidation/output-formatters/src',
),
'Consolidation\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/consolidation/log/src',
),
'Consolidation\\Config\\' =>
array (
0 => __DIR__ . '/..' . '/consolidation/config/src',
),
'Consolidation\\AnnotatedCommand\\' =>
array (
0 => __DIR__ . '/..' . '/consolidation/annotated-command/src',
),
);
public static $prefixesPsr0 = array (
'D' =>
array (
'Dflydev\\DotAccessData' =>
array (
0 => __DIR__ . '/..' . '/dflydev/dot-access-data/src',
),
),
'C' =>
array (
'Console' =>
array (
0 => __DIR__ . '/..' . '/pear/console_getopt',
),
),
'A' =>
array (
'Archive_Tar' =>
array (
0 => __DIR__ . '/..' . '/pear/archive_tar',
),
),
);
public static $fallbackDirsPsr0 = array (
0 => __DIR__ . '/..' . '/pear/pear-core-minimal/src',
);
public static $classMap = array (
'Archive_Tar' => __DIR__ . '/..' . '/pear/archive_tar/Archive/Tar.php',
'Console_Getopt' => __DIR__ . '/..' . '/pear/console_getopt/Console/Getopt.php',
'Consolidation\\AnnotatedCommand\\AnnotatedCommand' => __DIR__ . '/..' . '/consolidation/annotated-command/src/AnnotatedCommand.php',
'Consolidation\\AnnotatedCommand\\AnnotatedCommandFactory' => __DIR__ . '/..' . '/consolidation/annotated-command/src/AnnotatedCommandFactory.php',
'Consolidation\\AnnotatedCommand\\AnnotationData' => __DIR__ . '/..' . '/consolidation/annotated-command/src/AnnotationData.php',
'Consolidation\\AnnotatedCommand\\Cache\\CacheWrapper' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Cache/CacheWrapper.php',
'Consolidation\\AnnotatedCommand\\Cache\\NullCache' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Cache/NullCache.php',
'Consolidation\\AnnotatedCommand\\Cache\\SimpleCacheInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Cache/SimpleCacheInterface.php',
'Consolidation\\AnnotatedCommand\\CommandCreationListener' => __DIR__ . '/..' . '/consolidation/annotated-command/src/CommandCreationListener.php',
'Consolidation\\AnnotatedCommand\\CommandCreationListenerInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/CommandCreationListenerInterface.php',
'Consolidation\\AnnotatedCommand\\CommandData' => __DIR__ . '/..' . '/consolidation/annotated-command/src/CommandData.php',
'Consolidation\\AnnotatedCommand\\CommandError' => __DIR__ . '/..' . '/consolidation/annotated-command/src/CommandError.php',
'Consolidation\\AnnotatedCommand\\CommandFileDiscovery' => __DIR__ . '/..' . '/consolidation/annotated-command/src/CommandFileDiscovery.php',
'Consolidation\\AnnotatedCommand\\CommandInfoAltererInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/CommandInfoAltererInterface.php',
'Consolidation\\AnnotatedCommand\\CommandProcessor' => __DIR__ . '/..' . '/consolidation/annotated-command/src/CommandProcessor.php',
'Consolidation\\AnnotatedCommand\\CommandResult' => __DIR__ . '/..' . '/consolidation/annotated-command/src/CommandResult.php',
'Consolidation\\AnnotatedCommand\\Events\\CustomEventAwareInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Events/CustomEventAwareInterface.php',
'Consolidation\\AnnotatedCommand\\Events\\CustomEventAwareTrait' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Events/CustomEventAwareTrait.php',
'Consolidation\\AnnotatedCommand\\ExitCodeInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/ExitCodeInterface.php',
'Consolidation\\AnnotatedCommand\\Help\\HelpCommand' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Help/HelpCommand.php',
'Consolidation\\AnnotatedCommand\\Help\\HelpDocument' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Help/HelpDocument.php',
'Consolidation\\AnnotatedCommand\\Help\\HelpDocumentAlter' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Help/HelpDocumentAlter.php',
'Consolidation\\AnnotatedCommand\\Help\\HelpDocumentBuilder' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Help/HelpDocumentBuilder.php',
'Consolidation\\AnnotatedCommand\\Hooks\\AlterResultInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/AlterResultInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\CommandEventHookDispatcher' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/Dispatchers/CommandEventHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\ExtracterHookDispatcher' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/Dispatchers/ExtracterHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\HookDispatcher' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/Dispatchers/HookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\InitializeHookDispatcher' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/Dispatchers/InitializeHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\InteractHookDispatcher' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/Dispatchers/InteractHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\OptionsHookDispatcher' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/Dispatchers/OptionsHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\ProcessResultHookDispatcher' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/Dispatchers/ProcessResultHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\ReplaceCommandHookDispatcher' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/Dispatchers/ReplaceCommandHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\StatusDeterminerHookDispatcher' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/Dispatchers/StatusDeterminerHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\Dispatchers\\ValidateHookDispatcher' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/Dispatchers/ValidateHookDispatcher.php',
'Consolidation\\AnnotatedCommand\\Hooks\\ExtractOutputInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/ExtractOutputInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\HookManager' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/HookManager.php',
'Consolidation\\AnnotatedCommand\\Hooks\\InitializeHookInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/InitializeHookInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\InteractorInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/InteractorInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\OptionHookInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/OptionHookInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\ProcessResultInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/ProcessResultInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\StatusDeterminerInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/StatusDeterminerInterface.php',
'Consolidation\\AnnotatedCommand\\Hooks\\ValidatorInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Hooks/ValidatorInterface.php',
'Consolidation\\AnnotatedCommand\\Input\\StdinAwareInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Input/StdinAwareInterface.php',
'Consolidation\\AnnotatedCommand\\Input\\StdinAwareTrait' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Input/StdinAwareTrait.php',
'Consolidation\\AnnotatedCommand\\Input\\StdinHandler' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Input/StdinHandler.php',
'Consolidation\\AnnotatedCommand\\Options\\AlterOptionsCommandEvent' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Options/AlterOptionsCommandEvent.php',
'Consolidation\\AnnotatedCommand\\Options\\AutomaticOptionsProviderInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Options/AutomaticOptionsProviderInterface.php',
'Consolidation\\AnnotatedCommand\\Options\\PrepareFormatter' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Options/PrepareFormatter.php',
'Consolidation\\AnnotatedCommand\\Options\\PrepareTerminalWidthOption' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Options/PrepareTerminalWidthOption.php',
'Consolidation\\AnnotatedCommand\\OutputDataInterface' => __DIR__ . '/..' . '/consolidation/annotated-command/src/OutputDataInterface.php',
'Consolidation\\AnnotatedCommand\\ParameterInjection' => __DIR__ . '/..' . '/consolidation/annotated-command/src/ParameterInjection.php',
'Consolidation\\AnnotatedCommand\\ParameterInjector' => __DIR__ . '/..' . '/consolidation/annotated-command/src/ParameterInjector.php',
'Consolidation\\AnnotatedCommand\\Parser\\CommandInfo' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Parser/CommandInfo.php',
'Consolidation\\AnnotatedCommand\\Parser\\CommandInfoDeserializer' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Parser/CommandInfoDeserializer.php',
'Consolidation\\AnnotatedCommand\\Parser\\CommandInfoSerializer' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Parser/CommandInfoSerializer.php',
'Consolidation\\AnnotatedCommand\\Parser\\DefaultsWithDescriptions' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Parser/DefaultsWithDescriptions.php',
'Consolidation\\AnnotatedCommand\\Parser\\Internal\\BespokeDocBlockParser' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php',
'Consolidation\\AnnotatedCommand\\Parser\\Internal\\CommandDocBlockParserFactory' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Parser/Internal/CommandDocBlockParserFactory.php',
'Consolidation\\AnnotatedCommand\\Parser\\Internal\\CsvUtils' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Parser/Internal/CsvUtils.php',
'Consolidation\\AnnotatedCommand\\Parser\\Internal\\DocblockTag' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Parser/Internal/DocblockTag.php',
'Consolidation\\AnnotatedCommand\\Parser\\Internal\\FullyQualifiedClassCache' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Parser/Internal/FullyQualifiedClassCache.php',
'Consolidation\\AnnotatedCommand\\Parser\\Internal\\TagFactory' => __DIR__ . '/..' . '/consolidation/annotated-command/src/Parser/Internal/TagFactory.php',
'Consolidation\\AnnotatedCommand\\ResultWriter' => __DIR__ . '/..' . '/consolidation/annotated-command/src/ResultWriter.php',
'Consolidation\\Config\\Config' => __DIR__ . '/..' . '/consolidation/config/src/Config.php',
'Consolidation\\Config\\ConfigAwareInterface' => __DIR__ . '/..' . '/consolidation/config/src/ConfigAwareInterface.php',
'Consolidation\\Config\\ConfigAwareTrait' => __DIR__ . '/..' . '/consolidation/config/src/ConfigAwareTrait.php',
'Consolidation\\Config\\ConfigInterface' => __DIR__ . '/..' . '/consolidation/config/src/ConfigInterface.php',
'Consolidation\\Config\\GlobalOptionDefaultValuesInterface' => __DIR__ . '/..' . '/consolidation/config/src/GlobalOptionDefaultValuesInterface.php',
'Consolidation\\Config\\Inject\\ConfigForCommand' => __DIR__ . '/..' . '/consolidation/config/src/Inject/ConfigForCommand.php',
'Consolidation\\Config\\Inject\\ConfigForSetters' => __DIR__ . '/..' . '/consolidation/config/src/Inject/ConfigForSetters.php',
'Consolidation\\Config\\Loader\\ConfigLoader' => __DIR__ . '/..' . '/consolidation/config/src/Loader/ConfigLoader.php',
'Consolidation\\Config\\Loader\\ConfigLoaderInterface' => __DIR__ . '/..' . '/consolidation/config/src/Loader/ConfigLoaderInterface.php',
'Consolidation\\Config\\Loader\\ConfigProcessor' => __DIR__ . '/..' . '/consolidation/config/src/Loader/ConfigProcessor.php',
'Consolidation\\Config\\Loader\\YamlConfigLoader' => __DIR__ . '/..' . '/consolidation/config/src/Loader/YamlConfigLoader.php',
'Consolidation\\Config\\Util\\ArrayUtil' => __DIR__ . '/..' . '/consolidation/config/src/Util/ArrayUtil.php',
'Consolidation\\Config\\Util\\ConfigFallback' => __DIR__ . '/..' . '/consolidation/config/src/Util/ConfigFallback.php',
'Consolidation\\Config\\Util\\ConfigGroup' => __DIR__ . '/..' . '/consolidation/config/src/Util/ConfigGroup.php',
'Consolidation\\Config\\Util\\ConfigInterpolatorInterface' => __DIR__ . '/..' . '/consolidation/config/src/Util/ConfigInterpolatorInterface.php',
'Consolidation\\Config\\Util\\ConfigInterpolatorTrait' => __DIR__ . '/..' . '/consolidation/config/src/Util/ConfigInterpolatorTrait.php',
'Consolidation\\Config\\Util\\ConfigMerge' => __DIR__ . '/..' . '/consolidation/config/src/Util/ConfigMerge.php',
'Consolidation\\Config\\Util\\ConfigOverlay' => __DIR__ . '/..' . '/consolidation/config/src/Util/ConfigOverlay.php',
'Consolidation\\Config\\Util\\ConfigRuntimeInterface' => __DIR__ . '/..' . '/consolidation/config/src/Util/ConfigRuntimeInterface.php',
'Consolidation\\Config\\Util\\EnvConfig' => __DIR__ . '/..' . '/consolidation/config/src/Util/EnvConfig.php',
'Consolidation\\Config\\Util\\Interpolator' => __DIR__ . '/..' . '/consolidation/config/src/Util/Interpolator.php',
'Consolidation\\Log\\ConsoleLogLevel' => __DIR__ . '/..' . '/consolidation/log/src/ConsoleLogLevel.php',
'Consolidation\\Log\\LogOutputStyler' => __DIR__ . '/..' . '/consolidation/log/src/LogOutputStyler.php',
'Consolidation\\Log\\LogOutputStylerInterface' => __DIR__ . '/..' . '/consolidation/log/src/LogOutputStylerInterface.php',
'Consolidation\\Log\\Logger' => __DIR__ . '/..' . '/consolidation/log/src/Logger.php',
'Consolidation\\Log\\LoggerManager' => __DIR__ . '/..' . '/consolidation/log/src/LoggerManager.php',
'Consolidation\\Log\\StylableLoggerInterface' => __DIR__ . '/..' . '/consolidation/log/src/StylableLoggerInterface.php',
'Consolidation\\Log\\SymfonyLogOutputStyler' => __DIR__ . '/..' . '/consolidation/log/src/SymfonyLogOutputStyler.php',
'Consolidation\\Log\\UnstyledLogOutputStyler' => __DIR__ . '/..' . '/consolidation/log/src/UnstyledLogOutputStyler.php',
'Consolidation\\OutputFormatters\\Exception\\AbstractDataFormatException' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Exception/AbstractDataFormatException.php',
'Consolidation\\OutputFormatters\\Exception\\IncompatibleDataException' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Exception/IncompatibleDataException.php',
'Consolidation\\OutputFormatters\\Exception\\InvalidFormatException' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Exception/InvalidFormatException.php',
'Consolidation\\OutputFormatters\\Exception\\UnknownFieldException' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Exception/UnknownFieldException.php',
'Consolidation\\OutputFormatters\\Exception\\UnknownFormatException' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Exception/UnknownFormatException.php',
'Consolidation\\OutputFormatters\\FormatterManager' => __DIR__ . '/..' . '/consolidation/output-formatters/src/FormatterManager.php',
'Consolidation\\OutputFormatters\\Formatters\\CsvFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/CsvFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\FormatterAwareInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/FormatterAwareInterface.php',
'Consolidation\\OutputFormatters\\Formatters\\FormatterAwareTrait' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/FormatterAwareTrait.php',
'Consolidation\\OutputFormatters\\Formatters\\FormatterInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/FormatterInterface.php',
'Consolidation\\OutputFormatters\\Formatters\\HumanReadableFormat' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/HumanReadableFormat.php',
'Consolidation\\OutputFormatters\\Formatters\\JsonFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/JsonFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\ListFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/ListFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\MetadataFormatterInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/MetadataFormatterInterface.php',
'Consolidation\\OutputFormatters\\Formatters\\MetadataFormatterTrait' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/MetadataFormatterTrait.php',
'Consolidation\\OutputFormatters\\Formatters\\NoOutputFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/NoOutputFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\PrintRFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/PrintRFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\RenderDataInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/RenderDataInterface.php',
'Consolidation\\OutputFormatters\\Formatters\\RenderTableDataTrait' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/RenderTableDataTrait.php',
'Consolidation\\OutputFormatters\\Formatters\\SectionsFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/SectionsFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\SerializeFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/SerializeFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\StringFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/StringFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\TableFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/TableFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\TsvFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/TsvFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\VarDumpFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/VarDumpFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\VarExportFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/VarExportFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\XmlFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/XmlFormatter.php',
'Consolidation\\OutputFormatters\\Formatters\\YamlFormatter' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Formatters/YamlFormatter.php',
'Consolidation\\OutputFormatters\\Options\\FormatterOptions' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Options/FormatterOptions.php',
'Consolidation\\OutputFormatters\\Options\\OverrideOptionsInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Options/OverrideOptionsInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\AbstractListData' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/AbstractListData.php',
'Consolidation\\OutputFormatters\\StructuredData\\AbstractStructuredList' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/AbstractStructuredList.php',
'Consolidation\\OutputFormatters\\StructuredData\\AssociativeList' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/AssociativeList.php',
'Consolidation\\OutputFormatters\\StructuredData\\CallableRenderer' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/CallableRenderer.php',
'Consolidation\\OutputFormatters\\StructuredData\\ConversionInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/ConversionInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\FieldProcessor' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/FieldProcessor.php',
'Consolidation\\OutputFormatters\\StructuredData\\HelpDocument' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/HelpDocument.php',
'Consolidation\\OutputFormatters\\StructuredData\\ListDataFromKeys' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/ListDataFromKeys.php',
'Consolidation\\OutputFormatters\\StructuredData\\ListDataInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/ListDataInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\MetadataHolderInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/MetadataHolderInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\MetadataHolderTrait' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/MetadataHolderTrait.php',
'Consolidation\\OutputFormatters\\StructuredData\\MetadataInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/MetadataInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\NumericCellRenderer' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/NumericCellRenderer.php',
'Consolidation\\OutputFormatters\\StructuredData\\OriginalDataInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/OriginalDataInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\PropertyList' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/PropertyList.php',
'Consolidation\\OutputFormatters\\StructuredData\\RenderCellCollectionInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/RenderCellCollectionInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\RenderCellCollectionTrait' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/RenderCellCollectionTrait.php',
'Consolidation\\OutputFormatters\\StructuredData\\RenderCellInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/RenderCellInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\RestructureInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/RestructureInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\RowsOfFields' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/RowsOfFields.php',
'Consolidation\\OutputFormatters\\StructuredData\\RowsOfFieldsWithMetadata' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/RowsOfFieldsWithMetadata.php',
'Consolidation\\OutputFormatters\\StructuredData\\TableDataInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/TableDataInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\UnstructuredData' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/UnstructuredData.php',
'Consolidation\\OutputFormatters\\StructuredData\\UnstructuredInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/UnstructuredInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\UnstructuredListData' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/UnstructuredListData.php',
'Consolidation\\OutputFormatters\\StructuredData\\Xml\\DomDataInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/Xml/DomDataInterface.php',
'Consolidation\\OutputFormatters\\StructuredData\\Xml\\XmlSchema' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/Xml/XmlSchema.php',
'Consolidation\\OutputFormatters\\StructuredData\\Xml\\XmlSchemaInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/StructuredData/Xml/XmlSchemaInterface.php',
'Consolidation\\OutputFormatters\\Transformations\\DomToArraySimplifier' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/DomToArraySimplifier.php',
'Consolidation\\OutputFormatters\\Transformations\\OverrideRestructureInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/OverrideRestructureInterface.php',
'Consolidation\\OutputFormatters\\Transformations\\PropertyListTableTransformation' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/PropertyListTableTransformation.php',
'Consolidation\\OutputFormatters\\Transformations\\PropertyParser' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/PropertyParser.php',
'Consolidation\\OutputFormatters\\Transformations\\ReorderFields' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/ReorderFields.php',
'Consolidation\\OutputFormatters\\Transformations\\SimplifyToArrayInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/SimplifyToArrayInterface.php',
'Consolidation\\OutputFormatters\\Transformations\\StringTransformationInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/StringTransformationInterface.php',
'Consolidation\\OutputFormatters\\Transformations\\TableTransformation' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/TableTransformation.php',
'Consolidation\\OutputFormatters\\Transformations\\UnstructuredDataFieldAccessor' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/UnstructuredDataFieldAccessor.php',
'Consolidation\\OutputFormatters\\Transformations\\UnstructuredDataListTransformation' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/UnstructuredDataListTransformation.php',
'Consolidation\\OutputFormatters\\Transformations\\UnstructuredDataTransformation' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/UnstructuredDataTransformation.php',
'Consolidation\\OutputFormatters\\Transformations\\WordWrapper' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/WordWrapper.php',
'Consolidation\\OutputFormatters\\Transformations\\Wrap\\CalculateWidths' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/Wrap/CalculateWidths.php',
'Consolidation\\OutputFormatters\\Transformations\\Wrap\\ColumnWidths' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Transformations/Wrap/ColumnWidths.php',
'Consolidation\\OutputFormatters\\Validate\\ValidDataTypesInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Validate/ValidDataTypesInterface.php',
'Consolidation\\OutputFormatters\\Validate\\ValidDataTypesTrait' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Validate/ValidDataTypesTrait.php',
'Consolidation\\OutputFormatters\\Validate\\ValidationInterface' => __DIR__ . '/..' . '/consolidation/output-formatters/src/Validate/ValidationInterface.php',
'CssAtCharsetParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtCharsetToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtFontFaceDeclarationToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtFontFaceEndToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtFontFaceParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtFontFaceStartToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtImportParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtImportToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtKeyframesEndToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtKeyframesParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtKeyframesRulesetDeclarationToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtKeyframesRulesetEndToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtKeyframesRulesetStartToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtKeyframesStartToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtMediaEndToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtMediaParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtMediaStartToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtPageDeclarationToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtPageEndToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtPageParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtPageStartToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtVariablesDeclarationToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtVariablesEndToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtVariablesParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssAtVariablesStartToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssCommentParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssCommentToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssCompressColorValuesMinifierPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssCompressExpressionValuesMinifierPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssCompressUnitValuesMinifierPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssConvertFontWeightMinifierPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssConvertHslColorsMinifierPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssConvertLevel3AtKeyframesMinifierFilter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssConvertLevel3PropertiesMinifierFilter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssConvertNamedColorsMinifierPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssConvertRgbColorsMinifierPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssError' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssExpressionParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssImportImportsMinifierFilter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssMin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssMinifier' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssNullToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssOtbsFormatter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssParser' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssRemoveCommentsMinifierFilter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssRemoveEmptyAtBlocksMinifierFilter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssRemoveEmptyRulesetsMinifierFilter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssRemoveLastDelarationSemiColonMinifierFilter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssRulesetDeclarationToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssRulesetEndToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssRulesetParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssRulesetStartToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssSortRulesetPropertiesMinifierFilter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssStringParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssUrlParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssVariablesMinifierFilter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssVariablesMinifierPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'CssWhitesmithsFormatter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'Dflydev\\DotAccessData\\Data' => __DIR__ . '/..' . '/dflydev/dot-access-data/src/Dflydev/DotAccessData/Data.php',
'Dflydev\\DotAccessData\\DataInterface' => __DIR__ . '/..' . '/dflydev/dot-access-data/src/Dflydev/DotAccessData/DataInterface.php',
'Dflydev\\DotAccessData\\Util' => __DIR__ . '/..' . '/dflydev/dot-access-data/src/Dflydev/DotAccessData/Util.php',
'Grasmash\\Expander\\Expander' => __DIR__ . '/..' . '/grasmash/expander/src/Expander.php',
'Grasmash\\Expander\\Stringifier' => __DIR__ . '/..' . '/grasmash/expander/src/Stringifier.php',
'Grasmash\\Expander\\StringifierInterface' => __DIR__ . '/..' . '/grasmash/expander/src/StringifierInterface.php',
'Grasmash\\YamlExpander\\Expander' => __DIR__ . '/..' . '/grasmash/yaml-expander/src/Expander.php',
'Interop\\Container\\ContainerInterface' => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container/ContainerInterface.php',
'Interop\\Container\\Exception\\ContainerException' => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container/Exception/ContainerException.php',
'Interop\\Container\\Exception\\NotFoundException' => __DIR__ . '/..' . '/container-interop/container-interop/src/Interop/Container/Exception/NotFoundException.php',
'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
'League\\Container\\Argument\\ArgumentResolverInterface' => __DIR__ . '/..' . '/league/container/src/Argument/ArgumentResolverInterface.php',
'League\\Container\\Argument\\ArgumentResolverTrait' => __DIR__ . '/..' . '/league/container/src/Argument/ArgumentResolverTrait.php',
'League\\Container\\Argument\\RawArgument' => __DIR__ . '/..' . '/league/container/src/Argument/RawArgument.php',
'League\\Container\\Argument\\RawArgumentInterface' => __DIR__ . '/..' . '/league/container/src/Argument/RawArgumentInterface.php',
'League\\Container\\Container' => __DIR__ . '/..' . '/league/container/src/Container.php',
'League\\Container\\ContainerAwareInterface' => __DIR__ . '/..' . '/league/container/src/ContainerAwareInterface.php',
'League\\Container\\ContainerAwareTrait' => __DIR__ . '/..' . '/league/container/src/ContainerAwareTrait.php',
'League\\Container\\ContainerInterface' => __DIR__ . '/..' . '/league/container/src/ContainerInterface.php',
'League\\Container\\Definition\\AbstractDefinition' => __DIR__ . '/..' . '/league/container/src/Definition/AbstractDefinition.php',
'League\\Container\\Definition\\CallableDefinition' => __DIR__ . '/..' . '/league/container/src/Definition/CallableDefinition.php',
'League\\Container\\Definition\\ClassDefinition' => __DIR__ . '/..' . '/league/container/src/Definition/ClassDefinition.php',
'League\\Container\\Definition\\ClassDefinitionInterface' => __DIR__ . '/..' . '/league/container/src/Definition/ClassDefinitionInterface.php',
'League\\Container\\Definition\\DefinitionFactory' => __DIR__ . '/..' . '/league/container/src/Definition/DefinitionFactory.php',
'League\\Container\\Definition\\DefinitionFactoryInterface' => __DIR__ . '/..' . '/league/container/src/Definition/DefinitionFactoryInterface.php',
'League\\Container\\Definition\\DefinitionInterface' => __DIR__ . '/..' . '/league/container/src/Definition/DefinitionInterface.php',
'League\\Container\\Exception\\NotFoundException' => __DIR__ . '/..' . '/league/container/src/Exception/NotFoundException.php',
'League\\Container\\ImmutableContainerAwareInterface' => __DIR__ . '/..' . '/league/container/src/ImmutableContainerAwareInterface.php',
'League\\Container\\ImmutableContainerAwareTrait' => __DIR__ . '/..' . '/league/container/src/ImmutableContainerAwareTrait.php',
'League\\Container\\ImmutableContainerInterface' => __DIR__ . '/..' . '/league/container/src/ImmutableContainerInterface.php',
'League\\Container\\Inflector\\Inflector' => __DIR__ . '/..' . '/league/container/src/Inflector/Inflector.php',
'League\\Container\\Inflector\\InflectorAggregate' => __DIR__ . '/..' . '/league/container/src/Inflector/InflectorAggregate.php',
'League\\Container\\Inflector\\InflectorAggregateInterface' => __DIR__ . '/..' . '/league/container/src/Inflector/InflectorAggregateInterface.php',
'League\\Container\\ReflectionContainer' => __DIR__ . '/..' . '/league/container/src/ReflectionContainer.php',
'League\\Container\\ServiceProvider\\AbstractServiceProvider' => __DIR__ . '/..' . '/league/container/src/ServiceProvider/AbstractServiceProvider.php',
'League\\Container\\ServiceProvider\\AbstractSignatureServiceProvider' => __DIR__ . '/..' . '/league/container/src/ServiceProvider/AbstractSignatureServiceProvider.php',
'League\\Container\\ServiceProvider\\BootableServiceProviderInterface' => __DIR__ . '/..' . '/league/container/src/ServiceProvider/BootableServiceProviderInterface.php',
'League\\Container\\ServiceProvider\\ServiceProviderAggregate' => __DIR__ . '/..' . '/league/container/src/ServiceProvider/ServiceProviderAggregate.php',
'League\\Container\\ServiceProvider\\ServiceProviderAggregateInterface' => __DIR__ . '/..' . '/league/container/src/ServiceProvider/ServiceProviderAggregateInterface.php',
'League\\Container\\ServiceProvider\\ServiceProviderInterface' => __DIR__ . '/..' . '/league/container/src/ServiceProvider/ServiceProviderInterface.php',
'League\\Container\\ServiceProvider\\SignatureServiceProviderInterface' => __DIR__ . '/..' . '/league/container/src/ServiceProvider/SignatureServiceProviderInterface.php',
'OS_Guess' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/OS/Guess.php',
'PEAR' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR.php',
'PEAR_Error' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR.php',
'PEAR_ErrorStack' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php',
'PEAR_Exception' => __DIR__ . '/..' . '/pear/pear_exception/PEAR/Exception.php',
'Patchwork\\JSqueeze' => __DIR__ . '/..' . '/patchwork/jsqueeze/src/JSqueeze.php',
'Psr\\Container\\ContainerExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerExceptionInterface.php',
'Psr\\Container\\ContainerInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerInterface.php',
'Psr\\Container\\NotFoundExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/NotFoundExceptionInterface.php',
'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php',
'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php',
'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php',
'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php',
'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php',
'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php',
'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php',
'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php',
'Psr\\Log\\Test\\DummyTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
'Psr\\Log\\Test\\LoggerInterfaceTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
'Psr\\Log\\Test\\TestLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/TestLogger.php',
'Robo\\Application' => __DIR__ . '/../..' . '/src/Application.php',
'Robo\\ClassDiscovery\\AbstractClassDiscovery' => __DIR__ . '/../..' . '/src/ClassDiscovery/AbstractClassDiscovery.php',
'Robo\\ClassDiscovery\\ClassDiscoveryInterface' => __DIR__ . '/../..' . '/src/ClassDiscovery/ClassDiscoveryInterface.php',
'Robo\\ClassDiscovery\\RelativeNamespaceDiscovery' => __DIR__ . '/../..' . '/src/ClassDiscovery/RelativeNamespaceDiscovery.php',
'Robo\\Collection\\CallableTask' => __DIR__ . '/../..' . '/src/Collection/CallableTask.php',
'Robo\\Collection\\Collection' => __DIR__ . '/../..' . '/src/Collection/Collection.php',
'Robo\\Collection\\CollectionBuilder' => __DIR__ . '/../..' . '/src/Collection/CollectionBuilder.php',
'Robo\\Collection\\CollectionInterface' => __DIR__ . '/../..' . '/src/Collection/CollectionInterface.php',
'Robo\\Collection\\CollectionProcessHook' => __DIR__ . '/../..' . '/src/Collection/CollectionProcessHook.php',
'Robo\\Collection\\CompletionWrapper' => __DIR__ . '/../..' . '/src/Collection/CompletionWrapper.php',
'Robo\\Collection\\Element' => __DIR__ . '/../..' . '/src/Collection/Element.php',
'Robo\\Collection\\NestedCollectionInterface' => __DIR__ . '/../..' . '/src/Collection/NestedCollectionInterface.php',
'Robo\\Collection\\TaskForEach' => __DIR__ . '/../..' . '/src/Collection/TaskForEach.php',
'Robo\\Collection\\Temporary' => __DIR__ . '/../..' . '/src/Collection/Temporary.php',
'Robo\\Collection\\loadTasks' => __DIR__ . '/../..' . '/src/Collection/loadTasks.php',
'Robo\\Common\\BuilderAwareTrait' => __DIR__ . '/../..' . '/src/Common/BuilderAwareTrait.php',
'Robo\\Common\\CommandArguments' => __DIR__ . '/../..' . '/src/Common/CommandArguments.php',
'Robo\\Common\\CommandReceiver' => __DIR__ . '/../..' . '/src/Common/CommandReceiver.php',
'Robo\\Common\\ConfigAwareTrait' => __DIR__ . '/../..' . '/src/Common/ConfigAwareTrait.php',
'Robo\\Common\\DynamicParams' => __DIR__ . '/../..' . '/src/Common/DynamicParams.php',
'Robo\\Common\\ExecCommand' => __DIR__ . '/../..' . '/src/Common/ExecCommand.php',
'Robo\\Common\\ExecOneCommand' => __DIR__ . '/../..' . '/src/Common/ExecOneCommand.php',
'Robo\\Common\\ExecTrait' => __DIR__ . '/../..' . '/src/Common/ExecTrait.php',
'Robo\\Common\\IO' => __DIR__ . '/../..' . '/src/Common/IO.php',
'Robo\\Common\\InflectionTrait' => __DIR__ . '/../..' . '/src/Common/InflectionTrait.php',
'Robo\\Common\\InputAwareTrait' => __DIR__ . '/../..' . '/src/Common/InputAwareTrait.php',
'Robo\\Common\\OutputAdapter' => __DIR__ . '/../..' . '/src/Common/OutputAdapter.php',
'Robo\\Common\\OutputAwareTrait' => __DIR__ . '/../..' . '/src/Common/OutputAwareTrait.php',
'Robo\\Common\\ProcessExecutor' => __DIR__ . '/../..' . '/src/Common/ProcessExecutor.php',
'Robo\\Common\\ProcessUtils' => __DIR__ . '/../..' . '/src/Common/ProcessUtils.php',
'Robo\\Common\\ProgressIndicator' => __DIR__ . '/../..' . '/src/Common/ProgressIndicator.php',
'Robo\\Common\\ProgressIndicatorAwareTrait' => __DIR__ . '/../..' . '/src/Common/ProgressIndicatorAwareTrait.php',
'Robo\\Common\\ResourceExistenceChecker' => __DIR__ . '/../..' . '/src/Common/ResourceExistenceChecker.php',
'Robo\\Common\\TaskIO' => __DIR__ . '/../..' . '/src/Common/TaskIO.php',
'Robo\\Common\\TimeKeeper' => __DIR__ . '/../..' . '/src/Common/TimeKeeper.php',
'Robo\\Common\\Timer' => __DIR__ . '/../..' . '/src/Common/Timer.php',
'Robo\\Common\\VerbosityThresholdTrait' => __DIR__ . '/../..' . '/src/Common/VerbosityThresholdTrait.php',
'Robo\\Config' => __DIR__ . '/../..' . '/src/Config.php',
'Robo\\Config\\Config' => __DIR__ . '/../..' . '/src/Config/Config.php',
'Robo\\Config\\GlobalOptionDefaultValuesInterface' => __DIR__ . '/../..' . '/src/Config/GlobalOptionDefaultValuesInterface.php',
'Robo\\Contract\\BuilderAwareInterface' => __DIR__ . '/../..' . '/src/Contract/BuilderAwareInterface.php',
'Robo\\Contract\\CommandInterface' => __DIR__ . '/../..' . '/src/Contract/CommandInterface.php',
'Robo\\Contract\\CompletionInterface' => __DIR__ . '/../..' . '/src/Contract/CompletionInterface.php',
'Robo\\Contract\\ConfigAwareInterface' => __DIR__ . '/../..' . '/src/Contract/ConfigAwareInterface.php',
'Robo\\Contract\\IOAwareInterface' => __DIR__ . '/../..' . '/src/Contract/IOAwareInterface.php',
'Robo\\Contract\\InflectionInterface' => __DIR__ . '/../..' . '/src/Contract/InflectionInterface.php',
'Robo\\Contract\\OutputAdapterInterface' => __DIR__ . '/../..' . '/src/Contract/OutputAdapterInterface.php',
'Robo\\Contract\\OutputAwareInterface' => __DIR__ . '/../..' . '/src/Contract/OutputAwareInterface.php',
'Robo\\Contract\\PrintedInterface' => __DIR__ . '/../..' . '/src/Contract/PrintedInterface.php',
'Robo\\Contract\\ProgressIndicatorAwareInterface' => __DIR__ . '/../..' . '/src/Contract/ProgressIndicatorAwareInterface.php',
'Robo\\Contract\\ProgressInterface' => __DIR__ . '/../..' . '/src/Contract/ProgressInterface.php',
'Robo\\Contract\\RollbackInterface' => __DIR__ . '/../..' . '/src/Contract/RollbackInterface.php',
'Robo\\Contract\\SimulatedInterface' => __DIR__ . '/../..' . '/src/Contract/SimulatedInterface.php',
'Robo\\Contract\\TaskInterface' => __DIR__ . '/../..' . '/src/Contract/TaskInterface.php',
'Robo\\Contract\\VerbosityThresholdInterface' => __DIR__ . '/../..' . '/src/Contract/VerbosityThresholdInterface.php',
'Robo\\Contract\\WrappedTaskInterface' => __DIR__ . '/../..' . '/src/Contract/WrappedTaskInterface.php',
'Robo\\Exception\\AbortTasksException' => __DIR__ . '/../..' . '/src/Exception/AbortTasksException.php',
'Robo\\Exception\\TaskException' => __DIR__ . '/../..' . '/src/Exception/TaskException.php',
'Robo\\Exception\\TaskExitException' => __DIR__ . '/../..' . '/src/Exception/TaskExitException.php',
'Robo\\GlobalOptionsEventListener' => __DIR__ . '/../..' . '/src/GlobalOptionsEventListener.php',
'Robo\\LoadAllTasks' => __DIR__ . '/../..' . '/src/LoadAllTasks.php',
'Robo\\Log\\ResultPrinter' => __DIR__ . '/../..' . '/src/Log/ResultPrinter.php',
'Robo\\Log\\RoboLogLevel' => __DIR__ . '/../..' . '/src/Log/RoboLogLevel.php',
'Robo\\Log\\RoboLogStyle' => __DIR__ . '/../..' . '/src/Log/RoboLogStyle.php',
'Robo\\Log\\RoboLogger' => __DIR__ . '/../..' . '/src/Log/RoboLogger.php',
'Robo\\Result' => __DIR__ . '/../..' . '/src/Result.php',
'Robo\\ResultData' => __DIR__ . '/../..' . '/src/ResultData.php',
'Robo\\Robo' => __DIR__ . '/../..' . '/src/Robo.php',
'Robo\\Runner' => __DIR__ . '/../..' . '/src/Runner.php',
'Robo\\State\\Consumer' => __DIR__ . '/../..' . '/src/State/Consumer.php',
'Robo\\State\\Data' => __DIR__ . '/../..' . '/src/State/Data.php',
'Robo\\State\\StateAwareInterface' => __DIR__ . '/../..' . '/src/State/StateAwareInterface.php',
'Robo\\State\\StateAwareTrait' => __DIR__ . '/../..' . '/src/State/StateAwareTrait.php',
'Robo\\Symfony\\SymfonyStyleInjector' => __DIR__ . '/../..' . '/src/Symfony/SymfonyStyleInjector.php',
'Robo\\TaskAccessor' => __DIR__ . '/../..' . '/src/TaskAccessor.php',
'Robo\\TaskInfo' => __DIR__ . '/../..' . '/src/TaskInfo.php',
'Robo\\Task\\ApiGen\\ApiGen' => __DIR__ . '/../..' . '/src/Task/ApiGen/ApiGen.php',
'Robo\\Task\\ApiGen\\loadTasks' => __DIR__ . '/../..' . '/src/Task/ApiGen/loadTasks.php',
'Robo\\Task\\Archive\\Extract' => __DIR__ . '/../..' . '/src/Task/Archive/Extract.php',
'Robo\\Task\\Archive\\Pack' => __DIR__ . '/../..' . '/src/Task/Archive/Pack.php',
'Robo\\Task\\Archive\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Archive/loadTasks.php',
'Robo\\Task\\Assets\\CssPreprocessor' => __DIR__ . '/../..' . '/src/Task/Assets/CssPreprocessor.php',
'Robo\\Task\\Assets\\ImageMinify' => __DIR__ . '/../..' . '/src/Task/Assets/ImageMinify.php',
'Robo\\Task\\Assets\\Less' => __DIR__ . '/../..' . '/src/Task/Assets/Less.php',
'Robo\\Task\\Assets\\Minify' => __DIR__ . '/../..' . '/src/Task/Assets/Minify.php',
'Robo\\Task\\Assets\\Scss' => __DIR__ . '/../..' . '/src/Task/Assets/Scss.php',
'Robo\\Task\\Assets\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Assets/loadTasks.php',
'Robo\\Task\\BaseTask' => __DIR__ . '/../..' . '/src/Task/BaseTask.php',
'Robo\\Task\\Base\\Exec' => __DIR__ . '/../..' . '/src/Task/Base/Exec.php',
'Robo\\Task\\Base\\ExecStack' => __DIR__ . '/../..' . '/src/Task/Base/ExecStack.php',
'Robo\\Task\\Base\\ParallelExec' => __DIR__ . '/../..' . '/src/Task/Base/ParallelExec.php',
'Robo\\Task\\Base\\SymfonyCommand' => __DIR__ . '/../..' . '/src/Task/Base/SymfonyCommand.php',
'Robo\\Task\\Base\\Watch' => __DIR__ . '/../..' . '/src/Task/Base/Watch.php',
'Robo\\Task\\Base\\loadShortcuts' => __DIR__ . '/../..' . '/src/Task/Base/loadShortcuts.php',
'Robo\\Task\\Base\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Base/loadTasks.php',
'Robo\\Task\\Bower\\Base' => __DIR__ . '/../..' . '/src/Task/Bower/Base.php',
'Robo\\Task\\Bower\\Install' => __DIR__ . '/../..' . '/src/Task/Bower/Install.php',
'Robo\\Task\\Bower\\Update' => __DIR__ . '/../..' . '/src/Task/Bower/Update.php',
'Robo\\Task\\Bower\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Bower/loadTasks.php',
'Robo\\Task\\CommandStack' => __DIR__ . '/../..' . '/src/Task/CommandStack.php',
'Robo\\Task\\Composer\\Base' => __DIR__ . '/../..' . '/src/Task/Composer/Base.php',
'Robo\\Task\\Composer\\Config' => __DIR__ . '/../..' . '/src/Task/Composer/Config.php',
'Robo\\Task\\Composer\\CreateProject' => __DIR__ . '/../..' . '/src/Task/Composer/CreateProject.php',
'Robo\\Task\\Composer\\DumpAutoload' => __DIR__ . '/../..' . '/src/Task/Composer/DumpAutoload.php',
'Robo\\Task\\Composer\\Init' => __DIR__ . '/../..' . '/src/Task/Composer/Init.php',
'Robo\\Task\\Composer\\Install' => __DIR__ . '/../..' . '/src/Task/Composer/Install.php',
'Robo\\Task\\Composer\\Remove' => __DIR__ . '/../..' . '/src/Task/Composer/Remove.php',
'Robo\\Task\\Composer\\RequireDependency' => __DIR__ . '/../..' . '/src/Task/Composer/RequireDependency.php',
'Robo\\Task\\Composer\\Update' => __DIR__ . '/../..' . '/src/Task/Composer/Update.php',
'Robo\\Task\\Composer\\Validate' => __DIR__ . '/../..' . '/src/Task/Composer/Validate.php',
'Robo\\Task\\Composer\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Composer/loadTasks.php',
'Robo\\Task\\Development\\Changelog' => __DIR__ . '/../..' . '/src/Task/Development/Changelog.php',
'Robo\\Task\\Development\\GenerateMarkdownDoc' => __DIR__ . '/../..' . '/src/Task/Development/GenerateMarkdownDoc.php',
'Robo\\Task\\Development\\GenerateTask' => __DIR__ . '/../..' . '/src/Task/Development/GenerateTask.php',
'Robo\\Task\\Development\\GitHub' => __DIR__ . '/../..' . '/src/Task/Development/GitHub.php',
'Robo\\Task\\Development\\GitHubRelease' => __DIR__ . '/../..' . '/src/Task/Development/GitHubRelease.php',
'Robo\\Task\\Development\\OpenBrowser' => __DIR__ . '/../..' . '/src/Task/Development/OpenBrowser.php',
'Robo\\Task\\Development\\PackPhar' => __DIR__ . '/../..' . '/src/Task/Development/PackPhar.php',
'Robo\\Task\\Development\\PhpServer' => __DIR__ . '/../..' . '/src/Task/Development/PhpServer.php',
'Robo\\Task\\Development\\SemVer' => __DIR__ . '/../..' . '/src/Task/Development/SemVer.php',
'Robo\\Task\\Development\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Development/loadTasks.php',
'Robo\\Task\\Docker\\Base' => __DIR__ . '/../..' . '/src/Task/Docker/Base.php',
'Robo\\Task\\Docker\\Build' => __DIR__ . '/../..' . '/src/Task/Docker/Build.php',
'Robo\\Task\\Docker\\Commit' => __DIR__ . '/../..' . '/src/Task/Docker/Commit.php',
'Robo\\Task\\Docker\\Exec' => __DIR__ . '/../..' . '/src/Task/Docker/Exec.php',
'Robo\\Task\\Docker\\Pull' => __DIR__ . '/../..' . '/src/Task/Docker/Pull.php',
'Robo\\Task\\Docker\\Remove' => __DIR__ . '/../..' . '/src/Task/Docker/Remove.php',
'Robo\\Task\\Docker\\Result' => __DIR__ . '/../..' . '/src/Task/Docker/Result.php',
'Robo\\Task\\Docker\\Run' => __DIR__ . '/../..' . '/src/Task/Docker/Run.php',
'Robo\\Task\\Docker\\Start' => __DIR__ . '/../..' . '/src/Task/Docker/Start.php',
'Robo\\Task\\Docker\\Stop' => __DIR__ . '/../..' . '/src/Task/Docker/Stop.php',
'Robo\\Task\\Docker\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Docker/loadTasks.php',
'Robo\\Task\\File\\Concat' => __DIR__ . '/../..' . '/src/Task/File/Concat.php',
'Robo\\Task\\File\\Replace' => __DIR__ . '/../..' . '/src/Task/File/Replace.php',
'Robo\\Task\\File\\TmpFile' => __DIR__ . '/../..' . '/src/Task/File/TmpFile.php',
'Robo\\Task\\File\\Write' => __DIR__ . '/../..' . '/src/Task/File/Write.php',
'Robo\\Task\\File\\loadTasks' => __DIR__ . '/../..' . '/src/Task/File/loadTasks.php',
'Robo\\Task\\Filesystem\\BaseDir' => __DIR__ . '/../..' . '/src/Task/Filesystem/BaseDir.php',
'Robo\\Task\\Filesystem\\CleanDir' => __DIR__ . '/../..' . '/src/Task/Filesystem/CleanDir.php',
'Robo\\Task\\Filesystem\\CopyDir' => __DIR__ . '/../..' . '/src/Task/Filesystem/CopyDir.php',
'Robo\\Task\\Filesystem\\DeleteDir' => __DIR__ . '/../..' . '/src/Task/Filesystem/DeleteDir.php',
'Robo\\Task\\Filesystem\\FilesystemStack' => __DIR__ . '/../..' . '/src/Task/Filesystem/FilesystemStack.php',
'Robo\\Task\\Filesystem\\FlattenDir' => __DIR__ . '/../..' . '/src/Task/Filesystem/FlattenDir.php',
'Robo\\Task\\Filesystem\\MirrorDir' => __DIR__ . '/../..' . '/src/Task/Filesystem/MirrorDir.php',
'Robo\\Task\\Filesystem\\TmpDir' => __DIR__ . '/../..' . '/src/Task/Filesystem/TmpDir.php',
'Robo\\Task\\Filesystem\\WorkDir' => __DIR__ . '/../..' . '/src/Task/Filesystem/WorkDir.php',
'Robo\\Task\\Filesystem\\loadShortcuts' => __DIR__ . '/../..' . '/src/Task/Filesystem/loadShortcuts.php',
'Robo\\Task\\Filesystem\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Filesystem/loadTasks.php',
'Robo\\Task\\Gulp\\Base' => __DIR__ . '/../..' . '/src/Task/Gulp/Base.php',
'Robo\\Task\\Gulp\\Run' => __DIR__ . '/../..' . '/src/Task/Gulp/Run.php',
'Robo\\Task\\Gulp\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Gulp/loadTasks.php',
'Robo\\Task\\Npm\\Base' => __DIR__ . '/../..' . '/src/Task/Npm/Base.php',
'Robo\\Task\\Npm\\Install' => __DIR__ . '/../..' . '/src/Task/Npm/Install.php',
'Robo\\Task\\Npm\\Update' => __DIR__ . '/../..' . '/src/Task/Npm/Update.php',
'Robo\\Task\\Npm\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Npm/loadTasks.php',
'Robo\\Task\\Remote\\Rsync' => __DIR__ . '/../..' . '/src/Task/Remote/Rsync.php',
'Robo\\Task\\Remote\\Ssh' => __DIR__ . '/../..' . '/src/Task/Remote/Ssh.php',
'Robo\\Task\\Remote\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Remote/loadTasks.php',
'Robo\\Task\\Simulator' => __DIR__ . '/../..' . '/src/Task/Simulator.php',
'Robo\\Task\\StackBasedTask' => __DIR__ . '/../..' . '/src/Task/StackBasedTask.php',
'Robo\\Task\\Testing\\Atoum' => __DIR__ . '/../..' . '/src/Task/Testing/Atoum.php',
'Robo\\Task\\Testing\\Behat' => __DIR__ . '/../..' . '/src/Task/Testing/Behat.php',
'Robo\\Task\\Testing\\Codecept' => __DIR__ . '/../..' . '/src/Task/Testing/Codecept.php',
'Robo\\Task\\Testing\\PHPUnit' => __DIR__ . '/../..' . '/src/Task/Testing/PHPUnit.php',
'Robo\\Task\\Testing\\Phpspec' => __DIR__ . '/../..' . '/src/Task/Testing/Phpspec.php',
'Robo\\Task\\Testing\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Testing/loadTasks.php',
'Robo\\Task\\Vcs\\GitStack' => __DIR__ . '/../..' . '/src/Task/Vcs/GitStack.php',
'Robo\\Task\\Vcs\\HgStack' => __DIR__ . '/../..' . '/src/Task/Vcs/HgStack.php',
'Robo\\Task\\Vcs\\SvnStack' => __DIR__ . '/../..' . '/src/Task/Vcs/SvnStack.php',
'Robo\\Task\\Vcs\\loadShortcuts' => __DIR__ . '/../..' . '/src/Task/Vcs/loadShortcuts.php',
'Robo\\Task\\Vcs\\loadTasks' => __DIR__ . '/../..' . '/src/Task/Vcs/loadTasks.php',
'Robo\\Tasks' => __DIR__ . '/../..' . '/src/Tasks.php',
'SelfUpdate\\SelfUpdateCommand' => __DIR__ . '/..' . '/consolidation/self-update/src/SelfUpdateCommand.php',
'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php',
'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => __DIR__ . '/..' . '/symfony/console/CommandLoader/CommandLoaderInterface.php',
'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/ContainerCommandLoader.php',
'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/FactoryCommandLoader.php',
'Symfony\\Component\\Console\\Command\\Command' => __DIR__ . '/..' . '/symfony/console/Command/Command.php',
'Symfony\\Component\\Console\\Command\\HelpCommand' => __DIR__ . '/..' . '/symfony/console/Command/HelpCommand.php',
'Symfony\\Component\\Console\\Command\\ListCommand' => __DIR__ . '/..' . '/symfony/console/Command/ListCommand.php',
'Symfony\\Component\\Console\\Command\\LockableTrait' => __DIR__ . '/..' . '/symfony/console/Command/LockableTrait.php',
'Symfony\\Component\\Console\\ConsoleEvents' => __DIR__ . '/..' . '/symfony/console/ConsoleEvents.php',
'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => __DIR__ . '/..' . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php',
'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => __DIR__ . '/..' . '/symfony/console/Descriptor/ApplicationDescription.php',
'Symfony\\Component\\Console\\Descriptor\\Descriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/Descriptor.php',
'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => __DIR__ . '/..' . '/symfony/console/Descriptor/DescriptorInterface.php',
'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/JsonDescriptor.php',
'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/MarkdownDescriptor.php',
'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/TextDescriptor.php',
'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/XmlDescriptor.php',
'Symfony\\Component\\Console\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/console/EventListener/ErrorListener.php',
'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleCommandEvent.php',
'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleErrorEvent.php',
'Symfony\\Component\\Console\\Event\\ConsoleEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleEvent.php',
'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleTerminateEvent.php',
'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/CommandNotFoundException.php',
'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/console/Exception/ExceptionInterface.php',
'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidArgumentException.php',
'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidOptionException.php',
'Symfony\\Component\\Console\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/console/Exception/LogicException.php',
'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/NamespaceNotFoundException.php',
'Symfony\\Component\\Console\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/console/Exception/RuntimeException.php',
'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatter.php',
'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterInterface.php',
'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyle.php',
'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleInterface.php',
'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleStack.php',
'Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php',
'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DebugFormatterHelper.php',
'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DescriptorHelper.php',
'Symfony\\Component\\Console\\Helper\\Dumper' => __DIR__ . '/..' . '/symfony/console/Helper/Dumper.php',
'Symfony\\Component\\Console\\Helper\\FormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/FormatterHelper.php',
'Symfony\\Component\\Console\\Helper\\Helper' => __DIR__ . '/..' . '/symfony/console/Helper/Helper.php',
'Symfony\\Component\\Console\\Helper\\HelperInterface' => __DIR__ . '/..' . '/symfony/console/Helper/HelperInterface.php',
'Symfony\\Component\\Console\\Helper\\HelperSet' => __DIR__ . '/..' . '/symfony/console/Helper/HelperSet.php',
'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => __DIR__ . '/..' . '/symfony/console/Helper/InputAwareHelper.php',
'Symfony\\Component\\Console\\Helper\\ProcessHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProcessHelper.php',
'Symfony\\Component\\Console\\Helper\\ProgressBar' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressBar.php',
'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressIndicator.php',
'Symfony\\Component\\Console\\Helper\\QuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/QuestionHelper.php',
'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/SymfonyQuestionHelper.php',
'Symfony\\Component\\Console\\Helper\\Table' => __DIR__ . '/..' . '/symfony/console/Helper/Table.php',
'Symfony\\Component\\Console\\Helper\\TableCell' => __DIR__ . '/..' . '/symfony/console/Helper/TableCell.php',
'Symfony\\Component\\Console\\Helper\\TableRows' => __DIR__ . '/..' . '/symfony/console/Helper/TableRows.php',
'Symfony\\Component\\Console\\Helper\\TableSeparator' => __DIR__ . '/..' . '/symfony/console/Helper/TableSeparator.php',
'Symfony\\Component\\Console\\Helper\\TableStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableStyle.php',
'Symfony\\Component\\Console\\Input\\ArgvInput' => __DIR__ . '/..' . '/symfony/console/Input/ArgvInput.php',
'Symfony\\Component\\Console\\Input\\ArrayInput' => __DIR__ . '/..' . '/symfony/console/Input/ArrayInput.php',
'Symfony\\Component\\Console\\Input\\Input' => __DIR__ . '/..' . '/symfony/console/Input/Input.php',
'Symfony\\Component\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/symfony/console/Input/InputArgument.php',
'Symfony\\Component\\Console\\Input\\InputAwareInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputAwareInterface.php',
'Symfony\\Component\\Console\\Input\\InputDefinition' => __DIR__ . '/..' . '/symfony/console/Input/InputDefinition.php',
'Symfony\\Component\\Console\\Input\\InputInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputInterface.php',
'Symfony\\Component\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/symfony/console/Input/InputOption.php',
'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => __DIR__ . '/..' . '/symfony/console/Input/StreamableInputInterface.php',
'Symfony\\Component\\Console\\Input\\StringInput' => __DIR__ . '/..' . '/symfony/console/Input/StringInput.php',
'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => __DIR__ . '/..' . '/symfony/console/Logger/ConsoleLogger.php',
'Symfony\\Component\\Console\\Output\\BufferedOutput' => __DIR__ . '/..' . '/symfony/console/Output/BufferedOutput.php',
'Symfony\\Component\\Console\\Output\\ConsoleOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutput.php',
'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutputInterface.php',
'Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleSectionOutput.php',
'Symfony\\Component\\Console\\Output\\NullOutput' => __DIR__ . '/..' . '/symfony/console/Output/NullOutput.php',
'Symfony\\Component\\Console\\Output\\Output' => __DIR__ . '/..' . '/symfony/console/Output/Output.php',
'Symfony\\Component\\Console\\Output\\OutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/OutputInterface.php',
'Symfony\\Component\\Console\\Output\\StreamOutput' => __DIR__ . '/..' . '/symfony/console/Output/StreamOutput.php',
'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ChoiceQuestion.php',
'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ConfirmationQuestion.php',
'Symfony\\Component\\Console\\Question\\Question' => __DIR__ . '/..' . '/symfony/console/Question/Question.php',
'Symfony\\Component\\Console\\Style\\OutputStyle' => __DIR__ . '/..' . '/symfony/console/Style/OutputStyle.php',
'Symfony\\Component\\Console\\Style\\StyleInterface' => __DIR__ . '/..' . '/symfony/console/Style/StyleInterface.php',
'Symfony\\Component\\Console\\Style\\SymfonyStyle' => __DIR__ . '/..' . '/symfony/console/Style/SymfonyStyle.php',
'Symfony\\Component\\Console\\Terminal' => __DIR__ . '/..' . '/symfony/console/Terminal.php',
'Symfony\\Component\\Console\\Tester\\ApplicationTester' => __DIR__ . '/..' . '/symfony/console/Tester/ApplicationTester.php',
'Symfony\\Component\\Console\\Tester\\CommandTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandTester.php',
'Symfony\\Component\\Console\\Tester\\TesterTrait' => __DIR__ . '/..' . '/symfony/console/Tester/TesterTrait.php',
'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php',
'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php',
'Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/WrappedListener.php',
'Symfony\\Component\\EventDispatcher\\DependencyInjection\\AddEventAliasesPass' => __DIR__ . '/..' . '/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php',
'Symfony\\Component\\EventDispatcher\\DependencyInjection\\ExtractingEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php',
'Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass' => __DIR__ . '/..' . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php',
'Symfony\\Component\\EventDispatcher\\Event' => __DIR__ . '/..' . '/symfony/event-dispatcher/Event.php',
'Symfony\\Component\\EventDispatcher\\EventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventDispatcher.php',
'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventDispatcherInterface.php',
'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventSubscriberInterface.php',
'Symfony\\Component\\EventDispatcher\\GenericEvent' => __DIR__ . '/..' . '/symfony/event-dispatcher/GenericEvent.php',
'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/ImmutableEventDispatcher.php',
'Symfony\\Component\\EventDispatcher\\LegacyEventDispatcherProxy' => __DIR__ . '/..' . '/symfony/event-dispatcher/LegacyEventDispatcherProxy.php',
'Symfony\\Component\\EventDispatcher\\LegacyEventProxy' => __DIR__ . '/..' . '/symfony/event-dispatcher/LegacyEventProxy.php',
'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/ExceptionInterface.php',
'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/FileNotFoundException.php',
'Symfony\\Component\\Filesystem\\Exception\\IOException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOException.php',
'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOExceptionInterface.php',
'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/InvalidArgumentException.php',
'Symfony\\Component\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/symfony/filesystem/Filesystem.php',
'Symfony\\Component\\Finder\\Comparator\\Comparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/Comparator.php',
'Symfony\\Component\\Finder\\Comparator\\DateComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/DateComparator.php',
'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/NumberComparator.php',
'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/finder/Exception/AccessDeniedException.php',
'Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => __DIR__ . '/..' . '/symfony/finder/Exception/DirectoryNotFoundException.php',
'Symfony\\Component\\Finder\\Finder' => __DIR__ . '/..' . '/symfony/finder/Finder.php',
'Symfony\\Component\\Finder\\Gitignore' => __DIR__ . '/..' . '/symfony/finder/Gitignore.php',
'Symfony\\Component\\Finder\\Glob' => __DIR__ . '/..' . '/symfony/finder/Glob.php',
'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/CustomFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DateRangeFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DepthRangeFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FileTypeFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilecontentFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilenameFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/PathFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php',
'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SizeRangeFilterIterator.php',
'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SortableIterator.php',
'Symfony\\Component\\Finder\\SplFileInfo' => __DIR__ . '/..' . '/symfony/finder/SplFileInfo.php',
'Symfony\\Component\\Process\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/process/Exception/ExceptionInterface.php',
'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/process/Exception/InvalidArgumentException.php',
'Symfony\\Component\\Process\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/process/Exception/LogicException.php',
'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessFailedException.php',
'Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessSignaledException.php',
'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessTimedOutException.php',
'Symfony\\Component\\Process\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/process/Exception/RuntimeException.php',
'Symfony\\Component\\Process\\ExecutableFinder' => __DIR__ . '/..' . '/symfony/process/ExecutableFinder.php',
'Symfony\\Component\\Process\\InputStream' => __DIR__ . '/..' . '/symfony/process/InputStream.php',
'Symfony\\Component\\Process\\PhpExecutableFinder' => __DIR__ . '/..' . '/symfony/process/PhpExecutableFinder.php',
'Symfony\\Component\\Process\\PhpProcess' => __DIR__ . '/..' . '/symfony/process/PhpProcess.php',
'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/AbstractPipes.php',
'Symfony\\Component\\Process\\Pipes\\PipesInterface' => __DIR__ . '/..' . '/symfony/process/Pipes/PipesInterface.php',
'Symfony\\Component\\Process\\Pipes\\UnixPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/UnixPipes.php',
'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/WindowsPipes.php',
'Symfony\\Component\\Process\\Process' => __DIR__ . '/..' . '/symfony/process/Process.php',
'Symfony\\Component\\Process\\ProcessUtils' => __DIR__ . '/..' . '/symfony/process/ProcessUtils.php',
'Symfony\\Component\\Yaml\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/yaml/Command/LintCommand.php',
'Symfony\\Component\\Yaml\\Dumper' => __DIR__ . '/..' . '/symfony/yaml/Dumper.php',
'Symfony\\Component\\Yaml\\Escaper' => __DIR__ . '/..' . '/symfony/yaml/Escaper.php',
'Symfony\\Component\\Yaml\\Exception\\DumpException' => __DIR__ . '/..' . '/symfony/yaml/Exception/DumpException.php',
'Symfony\\Component\\Yaml\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/yaml/Exception/ExceptionInterface.php',
'Symfony\\Component\\Yaml\\Exception\\ParseException' => __DIR__ . '/..' . '/symfony/yaml/Exception/ParseException.php',
'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/yaml/Exception/RuntimeException.php',
'Symfony\\Component\\Yaml\\Inline' => __DIR__ . '/..' . '/symfony/yaml/Inline.php',
'Symfony\\Component\\Yaml\\Parser' => __DIR__ . '/..' . '/symfony/yaml/Parser.php',
'Symfony\\Component\\Yaml\\Tag\\TaggedValue' => __DIR__ . '/..' . '/symfony/yaml/Tag/TaggedValue.php',
'Symfony\\Component\\Yaml\\Unescaper' => __DIR__ . '/..' . '/symfony/yaml/Unescaper.php',
'Symfony\\Component\\Yaml\\Yaml' => __DIR__ . '/..' . '/symfony/yaml/Yaml.php',
'Symfony\\Contracts\\EventDispatcher\\Event' => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts/Event.php',
'Symfony\\Contracts\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts/EventDispatcherInterface.php',
'Symfony\\Contracts\\Service\\ResetInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ResetInterface.php',
'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceLocatorTrait.php',
'Symfony\\Contracts\\Service\\ServiceProviderInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceProviderInterface.php',
'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberInterface.php',
'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberTrait.php',
'Symfony\\Contracts\\Service\\Test\\ServiceLocatorTest' => __DIR__ . '/..' . '/symfony/service-contracts/Test/ServiceLocatorTest.php',
'Symfony\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php',
'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php',
'Symfony\\Polyfill\\Php73\\Php73' => __DIR__ . '/..' . '/symfony/polyfill-php73/Php73.php',
'System' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/System.php',
'aCssAtBlockEndToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'aCssAtBlockStartToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'aCssDeclarationToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'aCssFormatter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'aCssMinifierFilter' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'aCssMinifierPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'aCssParserPlugin' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'aCssRulesetEndToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'aCssRulesetStartToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
'aCssToken' => __DIR__ . '/..' . '/natxet/cssmin/src/CssMin.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit1040fe349fd04fbe8683abc5aae8aae1::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit1040fe349fd04fbe8683abc5aae8aae1::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit1040fe349fd04fbe8683abc5aae8aae1::$prefixesPsr0;
$loader->fallbackDirsPsr0 = ComposerStaticInit1040fe349fd04fbe8683abc5aae8aae1::$fallbackDirsPsr0;
$loader->classMap = ComposerStaticInit1040fe349fd04fbe8683abc5aae8aae1::$classMap;
}, null, ClassLoader::class);
}
}
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit1040fe349fd04fbe8683abc5aae8aae1
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit1040fe349fd04fbe8683abc5aae8aae1', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit1040fe349fd04fbe8683abc5aae8aae1', 'loadClassLoader'));
$includePaths = require __DIR__ . '/include_paths.php';
$includePaths[] = get_include_path();
set_include_path(implode(PATH_SEPARATOR, $includePaths));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit1040fe349fd04fbe8683abc5aae8aae1::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInit1040fe349fd04fbe8683abc5aae8aae1::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequire1040fe349fd04fbe8683abc5aae8aae1($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequire1040fe349fd04fbe8683abc5aae8aae1($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
);
<?php
/*
* Copyright (C) 2016 Nicolas Grekas - p@tchwork.com
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the (at your option):
* Apache License v2.0 (see provided LICENCE.ASL20 file), or
* GNU General Public License v2.0 (see provided LICENCE.GPLv2 file).
*/
namespace Patchwork;
/*
*
* This class shrinks Javascript code
* (a process called minification nowadays)
*
* Should work with most valid Javascript code,
* even when semi-colons are missing.
*
* Features:
* - Removes comments and white spaces.
* - Renames every local vars, typically to a single character.
* - Renames also global vars, methods and properties, but only if they
* are marked special by some naming convention. By default, special
* var names begin with one or more "$", or with a single "_".
* - Renames also local/global vars found in strings,
* but only if they are marked special.
* - Keep Microsoft's conditional comments.
* - Output is optimized for later HTTP compression.
*
* Notes:
* - Source code must be parse error free before processing.
* - In order to maximise later HTTP compression (deflate, gzip),
* new variables names are chosen by considering closures,
* variables' frequency and characters' frequency.
* - If you use with/eval then be careful.
*
* Bonus:
* - Replaces false/true by !1/!0
* - Replaces new Array/Object by []/{}
* - Merges consecutive "var" declarations with commas
* - Merges consecutive concatened strings
* - Fix a bug in Safari's parser (http://forums.asp.net/thread/1585609.aspx)
* - Can replace optional semi-colons by line feeds,
* thus facilitating output debugging.
* - Keep important comments marked with /*!...
* - Treats three semi-colons ;;; like single-line comments
* (http://dean.edwards.name/packer/2/usage/#triple-semi-colon).
* - Fix special catch scope across browsers
* - Work around buggy-handling of named function expressions in IE<=8
*
* TODO?
* - foo['bar'] => foo.bar
* - {'foo':'bar'} => {foo:'bar'}
* - Dead code removal (never used function)
* - Munge primitives: var WINDOW=window, etc.
*/
class JSqueeze
{
const
SPECIAL_VAR_PACKER = '(\$+[a-zA-Z_]|_[a-zA-Z0-9$])[a-zA-Z0-9_$]*';
public
$charFreq;
protected
$strings,
$closures,
$str0,
$str1,
$argFreq,
$specialVarRx,
$keepImportantComments,
$varRx = '(?:[a-zA-Z_$])[a-zA-Z0-9_$]*',
$reserved = array(
// Literals
'true','false','null',
// ES6
'break','case','class','catch','const','continue','debugger','default','delete','do','else','export','extends','finally','for','function','if','import','in','instanceof','new','return','super','switch','this','throw','try','typeof','var','void','while','with','yield',
// Future
'enum',
// Strict mode
'implements','package','protected','static','let','interface','private','public',
// Module
'await',
// Older standards
'abstract','boolean','byte','char','double','final','float','goto','int','long','native','short','synchronized','throws','transient','volatile',
);
public function __construct()
{
$this->reserved = array_flip($this->reserved);
$this->charFreq = array_fill(0, 256, 0);
}
/**
* Squeezes a JavaScript source code.
*
* Set $singleLine to false if you want optional
* semi-colons to be replaced by line feeds.
*
* Set $keepImportantComments to false if you want /*! comments to be removed.
*
* $specialVarRx defines the regular expression of special variables names
* for global vars, methods, properties and in string substitution.
* Set it to false if you don't want any.
*
* If the analysed javascript source contains a single line comment like
* this one, then the directive will overwrite $specialVarRx:
*
* // jsqueeze.specialVarRx = your_special_var_regexp_here
*
* Only the first directive is parsed, others are ignored. It is not possible
* to redefine $specialVarRx in the middle of the javascript source.
*
* Example:
* $parser = new JSqueeze;
* $squeezed_js = $parser->squeeze($fat_js);
*/
public function squeeze($code, $singleLine = true, $keepImportantComments = true, $specialVarRx = false)
{
$code = trim($code);
if ('' === $code) {
return '';
}
$this->argFreq = array(-1 => 0);
$this->specialVarRx = $specialVarRx;
$this->keepImportantComments = !!$keepImportantComments;
if (preg_match("#//[ \t]*jsqueeze\.specialVarRx[ \t]*=[ \t]*([\"']?)(.*)\\1#i", $code, $key)) {
if (!$key[1]) {
$key[2] = trim($key[2]);
$key[1] = strtolower($key[2]);
$key[1] = $key[1] && $key[1] != 'false' && $key[1] != 'none' && $key[1] != 'off';
}
$this->specialVarRx = $key[1] ? $key[2] : false;
}
// Remove capturing parentheses
$this->specialVarRx && $this->specialVarRx = preg_replace('/(?<!\\\\)((?:\\\\\\\\)*)\((?!\?)/', '(?:', $this->specialVarRx);
false !== strpos($code, "\r") && $code = strtr(str_replace("\r\n", "\n", $code), "\r", "\n");
false !== strpos($code, "\xC2\x85") && $code = str_replace("\xC2\x85", "\n", $code); // Next Line
false !== strpos($code, "\xE2\x80\xA8") && $code = str_replace("\xE2\x80\xA8", "\n", $code); // Line Separator
false !== strpos($code, "\xE2\x80\xA9") && $code = str_replace("\xE2\x80\xA9", "\n", $code); // Paragraph Separator
list($code, $this->strings) = $this->extractStrings($code);
list($code, $this->closures) = $this->extractClosures($code);
$key = "//''\"\"#0'"; // This crap has a wonderful property: it can not happen in any valid javascript, even in strings
$this->closures[$key] = &$code;
$tree = array($key => array('parent' => false));
$this->makeVars($code, $tree[$key], $key);
$this->renameVars($tree[$key], true);
$code = substr($tree[$key]['code'], 1);
$code = preg_replace("'\breturn !'", 'return!', $code);
$code = preg_replace("'\}(?=(else|while)[^\$.a-zA-Z0-9_])'", "}\r", $code);
$code = str_replace(array_keys($this->strings), array_values($this->strings), $code);
if ($singleLine) {
$code = strtr($code, "\n", ';');
} else {
$code = str_replace("\n", ";\n", $code);
}
false !== strpos($code, "\r") && $code = strtr(trim($code), "\r", "\n");
// Cleanup memory
$this->charFreq = array_fill(0, 256, 0);
$this->strings = $this->closures = $this->argFreq = array();
$this->str0 = $this->str1 = '';
return $code;
}
protected function extractStrings($f)
{
if ($cc_on = false !== strpos($f, '@cc_on')) {
// Protect conditional comments from being removed
$f = str_replace('#', '##', $f);
$f = str_replace('/*@', '1#@', $f);
$f = preg_replace("'//@([^\n]+)'", '2#@$1@#3', $f);
$f = str_replace('@*/', '@#1', $f);
}
$len = strlen($f);
$code = str_repeat(' ', $len);
$j = 0;
$strings = array();
$K = 0;
$instr = false;
$q = array(
"'", '"',
"'" => 0,
'"' => 0,
);
// Extract strings, removes comments
for ($i = 0; $i < $len; ++$i) {
if ($instr) {
if ('//' == $instr) {
if ("\n" == $f[$i]) {
$f[$i--] = ' ';
$instr = false;
}
} elseif ($f[$i] == $instr || ('/' == $f[$i] && "/'" == $instr)) {
if ('!' == $instr) {
} elseif ('*' == $instr) {
if ('/' == $f[$i + 1]) {
++$i;
$instr = false;
}
} else {
if ("/'" == $instr) {
while (isset($f[$i + 1]) && false !== strpos('gmi', $f[$i + 1])) {
$s[] = $f[$i++];
}
$s[] = $f[$i];
}
$instr = false;
}
} elseif ('*' == $instr) {
} elseif ('!' == $instr) {
if ('*' == $f[$i] && '/' == $f[$i + 1]) {
$s[] = "*/\r";
++$i;
$instr = false;
} elseif ("\n" == $f[$i]) {
$s[] = "\r";
} else {
$s[] = $f[$i];
}
} elseif ('\\' == $f[$i]) {
++$i;
if ("\n" != $f[$i]) {
isset($q[$f[$i]]) && ++$q[$f[$i]];
$s[] = '\\'.$f[$i];
}
} elseif ('[' == $f[$i] && "/'" == $instr) {
$instr = '/[';
$s[] = '[';
} elseif (']' == $f[$i] && '/[' == $instr) {
$instr = "/'";
$s[] = ']';
} elseif ("'" == $f[$i] || '"' == $f[$i]) {
++$q[$f[$i]];
$s[] = '\\'.$f[$i];
} else {
$s[] = $f[$i];
}
} else {
switch ($f[$i]) {
case ';':
// Remove triple semi-colon
if ($i > 0 && ';' == $f[$i - 1] && $i + 1 < $len && ';' == $f[$i + 1]) {
$f[$i] = $f[$i + 1] = '/';
} else {
$code[++$j] = ';';
break;
}
case '/':
if ('*' == $f[$i + 1]) {
++$i;
$instr = '*';
if ($this->keepImportantComments && '!' == $f[$i + 1]) {
++$i;
// no break here
} else {
break;
}
} elseif ('/' == $f[$i + 1]) {
++$i;
$instr = '//';
break;
} else {
$a = $j && (' ' == $code[$j] || "\x7F" == $code[$j]) ? $code[$j - 1] : $code[$j];
if (false !== strpos('-!%&;<=>~:^+|,()*?[{} ', $a)
|| (false !== strpos('oenfd', $a)
&& preg_match(
"'(?<![\$.a-zA-Z0-9_])(do|else|return|typeof|yield[ \x7F]?\*?)[ \x7F]?$'",
substr($code, $j - 7, 8)
))
) {
if (')' === $a && $j > 1) {
$a = 1;
$k = $j - (' ' == $code[$j] || "\x7F" == $code[$j]) - 1;
while ($k >= 0 && $a) {
if ('(' === $code[$k]) {
--$a;
} elseif (')' === $code[$k]) {
++$a;
}
--$k;
}
if (!preg_match("'(?<![\$.a-zA-Z0-9_])(if|for|while)[ \x7F]?$'", substr($code, 0, $k + 1))) {
$code[++$j] = '/';
break;
}
}
$key = "//''\"\"".$K++.$instr = "/'";
$a = $j;
$code .= $key;
while (isset($key[++$j - $a - 1])) {
$code[$j] = $key[$j - $a - 1];
}
--$j;
isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
$strings[$key] = array('/');
$s = &$strings[$key];
} else {
$code[++$j] = '/';
}
break;
}
case "'":
case '"':
$instr = $f[$i];
$key = "//''\"\"".$K++.('!' == $instr ? ']' : "'");
$a = $j;
$code .= $key;
while (isset($key[++$j - $a - 1])) {
$code[$j] = $key[$j - $a - 1];
}
--$j;
isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
$strings[$key] = array();
$s = &$strings[$key];
'!' == $instr && $s[] = "\r/*!";
break;
case "\n":
if ($j > 3) {
if (' ' == $code[$j] || "\x7F" == $code[$j]) {
--$j;
}
$code[++$j] =
false !== strpos('kend+-', $code[$j - 1])
&& preg_match(
"'(?:\+\+|--|(?<![\$.a-zA-Z0-9_])(break|continue|return|yield[ \x7F]?\*?))[ \x7F]?$'",
substr($code, $j - 8, 9)
)
? ';' : "\x7F";
break;
}
case "\t": $f[$i] = ' ';
case ' ':
if (!$j || ' ' == $code[$j] || "\x7F" == $code[$j]) {
break;
}
default:
$code[++$j] = $f[$i];
}
}
}
isset($s) && ($s = implode('', $s)) && $cc_on && $this->restoreCc($s);
unset($s);
$code = substr($code, 0, $j + 1);
$cc_on && $this->restoreCc($code, false);
// Protect wanted spaces and remove unwanted ones
$code = strtr($code, "\x7F", ' ');
$code = str_replace('- -', "-\x7F-", $code);
$code = str_replace('+ +', "+\x7F+", $code);
$code = preg_replace("'(\d)\s+\.\s*([a-zA-Z\$_[(])'", "$1\x7F.$2", $code);
$code = preg_replace("# ([-!%&;<=>~:.^+|,()*?[\]{}/']+)#", '$1', $code);
$code = preg_replace("#([-!%&;<=>~:.^+|,()*?[\]{}/]+) #", '$1', $code);
$cc_on && $code = preg_replace_callback("'//[^\'].*?@#3'", function ($m) { return strtr($m[0], ' ', "\x7F"); }, $code);
// Replace new Array/Object by []/{}
false !== strpos($code, 'new Array') && $code = preg_replace("'new Array(?:\(\)|([;\])},:]))'", '[]$1', $code);
false !== strpos($code, 'new Object') && $code = preg_replace("'new Object(?:\(\)|([;\])},:]))'", '{}$1', $code);
// Add missing semi-colons after curly braces
// This adds more semi-colons than strictly needed,
// but it seems that later gzipping is favorable to the repetition of "};"
$code = preg_replace("'\}(?![:,;.()\[\]}\|&]|(else|catch|finally|while)[^\$.a-zA-Z0-9_])'", '};', $code);
// Tag possible empty instruction for easy detection
$code = preg_replace("'(?<![\$.a-zA-Z0-9_])if\('", '1#(', $code);
$code = preg_replace("'(?<![\$.a-zA-Z0-9_])for\('", '2#(', $code);
$code = preg_replace("'(?<![\$.a-zA-Z0-9_])while\('", '3#(', $code);
$forPool = array();
$instrPool = array();
$s = 0;
$f = array();
$j = -1;
// Remove as much semi-colon as possible
$len = strlen($code);
for ($i = 0; $i < $len; ++$i) {
switch ($code[$i]) {
case '(':
if ($j >= 0 && "\n" == $f[$j]) {
$f[$j] = ';';
}
++$s;
if ($i && '#' == $code[$i - 1]) {
$instrPool[$s - 1] = 1;
if ('2' == $code[$i - 2]) {
$forPool[$s] = 1;
}
}
$f[++$j] = '(';
break;
case ']':
case ')':
if ($i + 1 < $len && !isset($forPool[$s]) && !isset($instrPool[$s - 1]) && preg_match("'[a-zA-Z0-9_\$]'", $code[$i + 1])) {
$f[$j] .= $code[$i];
$f[++$j] = "\n";
} else {
$f[++$j] = $code[$i];
}
if (')' == $code[$i]) {
unset($forPool[$s]);
--$s;
}
continue 2;
case '}':
if ("\n" == $f[$j]) {
$f[$j] = '}';
} else {
$f[++$j] = '}';
}
break;
case ';':
if (isset($forPool[$s]) || isset($instrPool[$s])) {
$f[++$j] = ';';
} elseif ($j >= 0 && "\n" != $f[$j] && ';' != $f[$j]) {
$f[++$j] = "\n";
}
break;
case '#':
switch ($f[$j]) {
case '1': $f[$j] = 'if'; break 2;
case '2': $f[$j] = 'for'; break 2;
case '3': $f[$j] = 'while'; break 2;
}
case '[';
if ($j >= 0 && "\n" == $f[$j]) {
$f[$j] = ';';
}
default: $f[++$j] = $code[$i];
}
unset($instrPool[$s]);
}
$f = implode('', $f);
$cc_on && $f = str_replace('@#3', "\r", $f);
// Fix "else ;" empty instructions
$f = preg_replace("'(?<![\$.a-zA-Z0-9_])else\n'", "\n", $f);
$r1 = array( // keywords with a direct object
'case','delete','do','else','function','in','instanceof','of','break',
'new','return','throw','typeof','var','void','yield','let','if',
'const','get','set',
);
$r2 = array( // keywords with a subject
'in','instanceof','of',
);
// Fix missing semi-colons
$f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])".implode(')(?<!(?<![a-zA-Z0-9_\$])', $r1).') (?!('.implode('|', $r2).")(?![a-zA-Z0-9_\$]))'", "\n", $f);
$f = preg_replace("'(?<!(?<![a-zA-Z0-9_\$])do)(?<!(?<![a-zA-Z0-9_\$])else) if\('", "\nif(", $f);
$f = preg_replace("'(?<=--|\+\+)(?<![a-zA-Z0-9_\$])(".implode('|', $r1).")(?![a-zA-Z0-9_\$])'", "\n$1", $f);
$f = preg_replace("'(?<![a-zA-Z0-9_\$])for\neach\('", 'for each(', $f);
$f = preg_replace("'(?<![a-zA-Z0-9_\$])\n(".implode('|', $r2).")(?![a-zA-Z0-9_\$])'", '$1', $f);
// Merge strings
if ($q["'"] > $q['"']) {
$q = array($q[1], $q[0]);
}
$f = preg_replace("#//''\"\"[0-9]+'#", $q[0].'$0'.$q[0], $f);
strpos($f, $q[0].'+'.$q[0]) && $f = str_replace($q[0].'+'.$q[0], '', $f);
$len = count($strings);
foreach ($strings as $r1 => &$r2) {
$r2 = "/'" == substr($r1, -2)
? str_replace(array("\\'", '\\"'), array("'", '"'), $r2)
: str_replace('\\'.$q[1], $q[1], $r2);
}
// Restore wanted spaces
$f = strtr($f, "\x7F", ' ');
return array($f, $strings);
}
protected function extractClosures($code)
{
$code = ';'.$code;
$this->argFreq[-1] += substr_count($code, '}catch(');
if ($this->argFreq[-1]) {
// Special catch scope handling
// FIXME: this implementation doesn't work with nested catch scopes who need
// access to their parent's caught variable (but who needs that?).
$f = preg_split("@}catch\(({$this->varRx})@", $code, -1, PREG_SPLIT_DELIM_CAPTURE);
$code = 'catch$scope$var'.mt_rand();
$this->specialVarRx = $this->specialVarRx ? '(?:'.$this->specialVarRx.'|'.preg_quote($code).')' : preg_quote($code);
$i = count($f) - 1;
while ($i) {
$c = 1;
$j = 0;
$l = strlen($f[$i]);
while ($c && $j < $l) {
$s = $f[$i][$j++];
$c += '(' == $s ? 1 : (')' == $s ? -1 : 0);
}
if (!$c) {
do {
$s = $f[$i][$j++];
$c += '{' == $s ? 1 : ('}' == $s ? -1 : 0);
} while ($c && $j < $l);
}
$c = preg_quote($f[$i - 1], '#');
$f[$i - 2] .= '}catch('.preg_replace("#([.,{]?)(?<![a-zA-Z0-9_\$@]){$c}\\b#", '$1'.$code, $f[$i - 1].substr($f[$i], 0, $j)).substr($f[$i], $j);
unset($f[$i--], $f[$i--]);
}
$code = $f[0];
}
$f = preg_split("'(?<![a-zA-Z0-9_\$])((?:function[ (]|get |set ).*?\{)'", $code, -1, PREG_SPLIT_DELIM_CAPTURE);
$i = count($f) - 1;
$closures = array();
while ($i) {
$c = 1;
$j = 0;
$l = strlen($f[$i]);
while ($c && $j < $l) {
$s = $f[$i][$j++];
$c += '{' == $s ? 1 : ('}' == $s ? -1 : 0);
}
switch (substr($f[$i - 2], -1)) {
default:
if (false !== $c = strpos($f[$i - 1], ' ', 8)) {
break;
}
case false: case "\n": case ';': case '{': case '}': case ')': case ']':
$c = strpos($f[$i - 1], '(', 4);
}
$l = "//''\"\"#$i'";
$code = substr($f[$i - 1], $c);
$closures[$l] = $code.substr($f[$i], 0, $j);
$f[$i - 2] .= substr($f[$i - 1], 0, $c).$l.substr($f[$i], $j);
if ('(){' !== $code) {
$j = substr_count($code, ',');
do {
isset($this->argFreq[$j]) ? ++$this->argFreq[$j] : $this->argFreq[$j] = 1;
} while ($j--);
}
$i -= 2;
}
return array($f[0], $closures);
}
protected function makeVars($closure, &$tree, $key)
{
$tree['code'] = &$closure;
$tree['nfe'] = false;
$tree['used'] = array();
$tree['local'] = array();
// Replace multiple "var" declarations by a single one
$closure = preg_replace_callback("'(?<=[\n\{\}])var [^\n\{\};]+(?:\nvar [^\n\{\};]+)+'", array($this, 'mergeVarDeclarations'), $closure);
// Get all local vars (functions, arguments and "var" prefixed)
$vars = &$tree['local'];
if (preg_match("'^( [^(]*)?\((.*?)\)\{'", $closure, $v)) {
if ($v[1]) {
$vars[$tree['nfe'] = substr($v[1], 1)] = -1;
$tree['parent']['local'][';'.$key] = &$vars[$tree['nfe']];
}
if ($v[2]) {
$i = 0;
$v = explode(',', $v[2]);
foreach ($v as $w) {
$vars[$w] = $this->argFreq[$i++] - 1; // Give a bonus to argument variables
}
}
}
$v = preg_split("'(?<![\$.a-zA-Z0-9_])var '", $closure);
if ($i = count($v) - 1) {
$w = array();
while ($i) {
$j = $c = 0;
$l = strlen($v[$i]);
while ($j < $l) {
switch ($v[$i][$j]) {
case '(': case '[': case '{':
++$c;
break;
case ')': case ']': case '}':
if ($c-- <= 0) {
break 2;
}
break;
case ';': case "\n":
if (!$c) {
break 2;
}
default:
$c || $w[] = $v[$i][$j];
}
++$j;
}
$w[] = ',';
--$i;
}
$v = explode(',', implode('', $w));
foreach ($v as $w) {
if (preg_match("'^{$this->varRx}'", $w, $v)) {
isset($vars[$v[0]]) || $vars[$v[0]] = 0;
}
}
}
if (preg_match_all("@function ({$this->varRx})//''\"\"#@", $closure, $v)) {
foreach ($v[1] as $w) {
isset($vars[$w]) || $vars[$w] = 0;
}
}
if ($this->argFreq[-1] && preg_match_all("@}catch\(({$this->varRx})@", $closure, $v)) {
$v[0] = array();
foreach ($v[1] as $w) {
isset($v[0][$w]) ? ++$v[0][$w] : $v[0][$w] = 1;
}
foreach ($v[0] as $w => $v) {
$vars[$w] = $this->argFreq[-1] - $v;
}
}
// Get all used vars, local and non-local
$vars = &$tree['used'];
if (preg_match_all("#([.,{]?(?:[gs]et )?)(?<![a-zA-Z0-9_\$])({$this->varRx})(:?)#", $closure, $w, PREG_SET_ORDER)) {
foreach ($w as $k) {
if (isset($k[1][0]) && (',' === $k[1][0] || '{' === $k[1][0])) {
if (':' === $k[3]) {
$k = '.'.$k[2];
} elseif ('get ' === substr($k[1], 1, 4) || 'set ' === substr($k[1], 1, 4)) {
++$this->charFreq[ord($k[1][1])]; // "g" or "s"
++$this->charFreq[101]; // "e"
++$this->charFreq[116]; // "t"
$k = '.'.$k[2];
} else {
$k = $k[2];
}
} else {
$k = $k[1].$k[2];
}
isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1;
}
}
if (preg_match_all("#//''\"\"[0-9]+(?:['!]|/')#", $closure, $w)) {
foreach ($w[0] as $a) {
$v = "'" === substr($a, -1) && "/'" !== substr($a, -2) && $this->specialVarRx
? preg_split("#([.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?)#", $this->strings[$a], -1, PREG_SPLIT_DELIM_CAPTURE)
: array($this->strings[$a]);
$a = count($v);
for ($i = 0; $i < $a; ++$i) {
$k = $v[$i];
if (1 === $i % 2) {
if (',' === $k[0] || '{' === $k[0]) {
if (':' === substr($k, -1)) {
$k = '.'.substr($k, 1, -1);
} elseif ('get ' === substr($k, 1, 4) || 'set ' === substr($k, 1, 4)) {
++$this->charFreq[ord($k[1])]; // "g" or "s"
++$this->charFreq[101]; // "e"
++$this->charFreq[116]; // "t"
$k = '.'.substr($k, 5);
} else {
$k = substr($k, 1);
}
} elseif (':' === substr($k, -1)) {
$k = substr($k, 0, -1);
}
$w = &$tree;
while (isset($w['parent']) && !(isset($w['used'][$k]) || isset($w['local'][$k]))) {
$w = &$w['parent'];
}
(isset($w['used'][$k]) || isset($w['local'][$k])) && (isset($vars[$k]) ? ++$vars[$k] : $vars[$k] = 1);
unset($w);
}
if (0 === $i % 2 || !isset($vars[$k])) {
foreach (count_chars($v[$i], 1) as $k => $w) {
$this->charFreq[$k] += $w;
}
}
}
}
}
// Propagate the usage number to parents
foreach ($vars as $w => $a) {
$k = &$tree;
$chain = array();
do {
$vars = &$k['local'];
$chain[] = &$k;
if (isset($vars[$w])) {
unset($k['used'][$w]);
if (isset($vars[$w])) {
$vars[$w] += $a;
} else {
$vars[$w] = $a;
}
$a = false;
break;
}
} while ($k['parent'] && $k = &$k['parent']);
if ($a && !$k['parent']) {
if (isset($vars[$w])) {
$vars[$w] += $a;
} else {
$vars[$w] = $a;
}
}
if (isset($tree['used'][$w]) && isset($vars[$w])) {
foreach ($chain as &$b) {
isset($b['local'][$w]) || $b['used'][$w] = &$vars[$w];
}
}
}
// Analyse childs
$tree['childs'] = array();
$vars = &$tree['childs'];
if (preg_match_all("@//''\"\"#[0-9]+'@", $closure, $w)) {
foreach ($w[0] as $a) {
$vars[$a] = array('parent' => &$tree);
$this->makeVars($this->closures[$a], $vars[$a], $a);
}
}
}
protected function mergeVarDeclarations($m)
{
return str_replace("\nvar ", ',', $m[0]);
}
protected function renameVars(&$tree, $root)
{
if ($root) {
$tree['local'] += $tree['used'];
$tree['used'] = array();
foreach ($tree['local'] as $k => $v) {
if ('.' == $k[0]) {
$k = substr($k, 1);
}
if ('true' === $k) {
$this->charFreq[48] += $v;
} elseif ('false' === $k) {
$this->charFreq[49] += $v;
} elseif (!$this->specialVarRx || !preg_match("#^{$this->specialVarRx}$#", $k)) {
foreach (count_chars($k, 1) as $k => $w) {
$this->charFreq[$k] += $w * $v;
}
} elseif (2 == strlen($k)) {
$tree['used'][] = $k[1];
}
}
$this->charFreq = $this->rsort($this->charFreq);
$this->str0 = '';
$this->str1 = '';
foreach ($this->charFreq as $k => $v) {
if (!$v) {
break;
}
$v = chr($k);
if ((64 < $k && $k < 91) || (96 < $k && $k < 123)) { // A-Z a-z
$this->str0 .= $v;
$this->str1 .= $v;
} elseif (47 < $k && $k < 58) { // 0-9
$this->str1 .= $v;
}
}
if ('' === $this->str0) {
$this->str0 = 'claspemitdbfrugnjvhowkxqyzCLASPEMITDBFRUGNJVHOWKXQYZ';
$this->str1 = $this->str0.'0123456789';
}
foreach ($tree['local'] as $var => $root) {
if ('.' != substr($var, 0, 1) && isset($tree['local'][".{$var}"])) {
$tree['local'][$var] += $tree['local'][".{$var}"];
}
}
foreach ($tree['local'] as $var => $root) {
if ('.' == substr($var, 0, 1) && isset($tree['local'][substr($var, 1)])) {
$tree['local'][$var] = $tree['local'][substr($var, 1)];
}
}
$tree['local'] = $this->rsort($tree['local']);
foreach ($tree['local'] as $var => $root) {
switch (substr($var, 0, 1)) {
case '.':
if (!isset($tree['local'][substr($var, 1)])) {
$tree['local'][$var] = '#'.($this->specialVarRx && 3 < strlen($var) && preg_match("'^\.{$this->specialVarRx}$'", $var) ? $this->getNextName($tree).'$' : substr($var, 1));
}
break;
case ';': $tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree);
case '#': break;
default:
$root = $this->specialVarRx && 2 < strlen($var) && preg_match("'^{$this->specialVarRx}$'", $var) ? $this->getNextName($tree).'$' : $var;
$tree['local'][$var] = $root;
if (isset($tree['local'][".{$var}"])) {
$tree['local'][".{$var}"] = '#'.$root;
}
}
}
foreach ($tree['local'] as $var => $root) {
$tree['local'][$var] = preg_replace("'^#'", '.', $tree['local'][$var]);
}
} else {
$tree['local'] = $this->rsort($tree['local']);
if (false !== $tree['nfe']) {
$tree['used'][] = $tree['local'][$tree['nfe']];
}
foreach ($tree['local'] as $var => $root) {
if ($tree['nfe'] !== $var) {
$tree['local'][$var] = 0 === $root ? '' : $this->getNextName($tree);
}
}
}
$this->local_tree = &$tree['local'];
$this->used_tree = &$tree['used'];
$tree['code'] = preg_replace_callback("#[.,{ ]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->varRx}:?#", array($this, 'getNewName'), $tree['code']);
if ($this->specialVarRx && preg_match_all("#//''\"\"[0-9]+'#", $tree['code'], $b)) {
foreach ($b[0] as $a) {
$this->strings[$a] = preg_replace_callback(
"#[.,{]?(?:[gs]et )?(?<![a-zA-Z0-9_\$@]){$this->specialVarRx}:?#",
array($this, 'getNewName'),
$this->strings[$a]
);
}
}
foreach ($tree['childs'] as $a => &$b) {
$this->renameVars($b, false);
$tree['code'] = str_replace($a, $b['code'], $tree['code']);
unset($tree['childs'][$a]);
}
}
protected function getNewName($m)
{
$m = $m[0];
$pre = '.' === $m[0] ? '.' : '';
$post = '';
if (',' === $m[0] || '{' === $m[0] || ' ' === $m[0]) {
$pre = $m[0];
if (':' === substr($m, -1)) {
$post = ':';
$m = (' ' !== $m[0] ? '.' : '').substr($m, 1, -1);
} elseif ('get ' === substr($m, 1, 4) || 'set ' === substr($m, 1, 4)) {
$pre .= substr($m, 1, 4);
$m = '.'.substr($m, 5);
} else {
$m = substr($m, 1);
}
} elseif (':' === substr($m, -1)) {
$post = ':';
$m = substr($m, 0, -1);
}
$post = (isset($this->reserved[$m])
? ('true' === $m ? '!0' : ('false' === $m ? '!1' : $m))
: (
isset($this->local_tree[$m])
? $this->local_tree[$m]
: (
isset($this->used_tree[$m])
? $this->used_tree[$m]
: $m
)
)
).$post;
return '' === $post ? '' : ($pre.('.' === $post[0] ? substr($post, 1) : $post));
}
protected function getNextName(&$tree = array(), &$counter = false)
{
if (false === $counter) {
$counter = &$tree['counter'];
isset($counter) || $counter = -1;
$exclude = array_flip($tree['used']);
} else {
$exclude = $tree;
}
++$counter;
$len0 = strlen($this->str0);
$len1 = strlen($this->str0);
$name = $this->str0[$counter % $len0];
$i = intval($counter / $len0) - 1;
while ($i >= 0) {
$name .= $this->str1[ $i % $len1 ];
$i = intval($i / $len1) - 1;
}
return !(isset($this->reserved[$name]) || isset($exclude[$name])) ? $name : $this->getNextName($exclude, $counter);
}
protected function restoreCc(&$s, $lf = true)
{
$lf && $s = str_replace('@#3', '', $s);
$s = str_replace('@#1', '@*/', $s);
$s = str_replace('2#@', '//@', $s);
$s = str_replace('1#@', '/*@', $s);
$s = str_replace('##', '#', $s);
}
private function rsort($array)
{
if (!$array) {
return $array;
}
$i = 0;
$tuples = array();
foreach ($array as $k => &$v) {
$tuples[] = array(++$i, $k, &$v);
}
usort($tuples, function ($a, $b) {
if ($b[2] > $a[2]) {
return 1;
}
if ($b[2] < $a[2]) {
return -1;
}
if ($b[0] > $a[0]) {
return -1;
}
if ($b[0] < $a[0]) {
return 1;
}
return 0;
});
$array = array();
foreach ($tuples as $t) {
$array[$t[1]] = &$t[2];
}
return $array;
}
}
<?php
namespace League\Container;
trait ContainerAwareTrait
{
/**
* @var \League\Container\ContainerInterface
*/
protected $container;
/**
* Set a container.
*
* @param \League\Container\ContainerInterface $container
* @return $this
*/
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
return $this;
}
/**
* Get the container.
*
* @return \League\Container\ContainerInterface
*/
public function getContainer()
{
return $this->container;
}
}
<?php
namespace League\Container;
use Interop\Container\ContainerInterface as InteropContainerInterface;
use League\Container\Argument\RawArgumentInterface;
use League\Container\Definition\DefinitionFactory;
use League\Container\Definition\DefinitionFactoryInterface;
use League\Container\Definition\DefinitionInterface;
use League\Container\Exception\NotFoundException;
use League\Container\Inflector\InflectorAggregate;
use League\Container\Inflector\InflectorAggregateInterface;
use League\Container\ServiceProvider\ServiceProviderAggregate;
use League\Container\ServiceProvider\ServiceProviderAggregateInterface;
class Container implements ContainerInterface
{
/**
* @var \League\Container\Definition\DefinitionFactoryInterface
*/
protected $definitionFactory;
/**
* @var \League\Container\Definition\DefinitionInterface[]
*/
protected $definitions = [];
/**
* @var \League\Container\Definition\DefinitionInterface[]
*/
protected $sharedDefinitions = [];
/**
* @var \League\Container\Inflector\InflectorAggregateInterface
*/
protected $inflectors;
/**
* @var \League\Container\ServiceProvider\ServiceProviderAggregateInterface
*/
protected $providers;
/**
* @var array
*/
protected $shared = [];
/**
* @var \Interop\Container\ContainerInterface[]
*/
protected $delegates = [];
/**
* Constructor.
*
* @param \League\Container\ServiceProvider\ServiceProviderAggregateInterface|null $providers
* @param \League\Container\Inflector\InflectorAggregateInterface|null $inflectors
* @param \League\Container\Definition\DefinitionFactoryInterface|null $definitionFactory
*/
public function __construct(
ServiceProviderAggregateInterface $providers = null,
InflectorAggregateInterface $inflectors = null,
DefinitionFactoryInterface $definitionFactory = null
) {
// set required dependencies
$this->providers = (is_null($providers))
? (new ServiceProviderAggregate)->setContainer($this)
: $providers->setContainer($this);
$this->inflectors = (is_null($inflectors))
? (new InflectorAggregate)->setContainer($this)
: $inflectors->setContainer($this);
$this->definitionFactory = (is_null($definitionFactory))
? (new DefinitionFactory)->setContainer($this)
: $definitionFactory->setContainer($this);
}
/**
* {@inheritdoc}
*/
public function get($alias, array $args = [])
{
try {
return $this->getFromThisContainer($alias, $args);
} catch (NotFoundException $exception) {
if ($this->providers->provides($alias)) {
$this->providers->register($alias);
return $this->getFromThisContainer($alias, $args);
}
$resolved = $this->getFromDelegate($alias, $args);
return $this->inflectors->inflect($resolved);
}
}
/**
* {@inheritdoc}
*/
public function has($alias)
{
if (array_key_exists($alias, $this->definitions) || $this->hasShared($alias)) {
return true;
}
if ($this->providers->provides($alias)) {
return true;
}
return $this->hasInDelegate($alias);
}
/**
* Returns a boolean to determine if the container has a shared instance of an alias.
*
* @param string $alias
* @param boolean $resolved
* @return boolean
*/
public function hasShared($alias, $resolved = false)
{
$shared = ($resolved === false) ? array_merge($this->shared, $this->sharedDefinitions) : $this->shared;
return (array_key_exists($alias, $shared));
}
/**
* {@inheritdoc}
*/
public function add($alias, $concrete = null, $share = false)
{
unset($this->shared[$alias]);
unset($this->definitions[$alias]);
unset($this->sharedDefinitions[$alias]);
if (is_null($concrete)) {
$concrete = $alias;
}
$definition = $this->definitionFactory->getDefinition($alias, $concrete);
if ($definition instanceof DefinitionInterface) {
if ($share === false) {
$this->definitions[$alias] = $definition;
} else {
$this->sharedDefinitions[$alias] = $definition;
}
return $definition;
}
// dealing with a value that cannot build a definition
$this->shared[$alias] = $concrete;
}
/**
* {@inheritdoc}
*/
public function share($alias, $concrete = null)
{
return $this->add($alias, $concrete, true);
}
/**
* {@inheritdoc}
*/
public function addServiceProvider($provider)
{
$this->providers->add($provider);
return $this;
}
/**
* {@inheritdoc}
*/
public function extend($alias)
{
if ($this->providers->provides($alias)) {
$this->providers->register($alias);
}
if (array_key_exists($alias, $this->definitions)) {
return $this->definitions[$alias];
}
if (array_key_exists($alias, $this->sharedDefinitions)) {
return $this->sharedDefinitions[$alias];
}
throw new NotFoundException(
sprintf('Unable to extend alias (%s) as it is not being managed as a definition', $alias)
);
}
/**
* {@inheritdoc}
*/
public function inflector($type, callable $callback = null)
{
return $this->inflectors->add($type, $callback);
}
/**
* {@inheritdoc}
*/
public function call(callable $callable, array $args = [])
{
return (new ReflectionContainer)->setContainer($this)->call($callable, $args);
}
/**
* Delegate a backup container to be checked for services if it
* cannot be resolved via this container.
*
* @param \Interop\Container\ContainerInterface $container
* @return $this
*/
public function delegate(InteropContainerInterface $container)
{
$this->delegates[] = $container;
if ($container instanceof ImmutableContainerAwareInterface) {
$container->setContainer($this);
}
return $this;
}
/**
* Returns true if service is registered in one of the delegated backup containers.
*
* @param string $alias
* @return boolean
*/
public function hasInDelegate($alias)
{
foreach ($this->delegates as $container) {
if ($container->has($alias)) {
return true;
}
}
return false;
}
/**
* Attempt to get a service from the stack of delegated backup containers.
*
* @param string $alias
* @param array $args
* @return mixed
*/
protected function getFromDelegate($alias, array $args = [])
{
foreach ($this->delegates as $container) {
if ($container->has($alias)) {
return $container->get($alias, $args);
}
continue;
}
throw new NotFoundException(
sprintf('Alias (%s) is not being managed by the container', $alias)
);
}
/**
* Get a service that has been registered in this container.
*
* @param string $alias
* @param array $args
* @return mixed
*/
protected function getFromThisContainer($alias, array $args = [])
{
if ($this->hasShared($alias, true)) {
$shared = $this->inflectors->inflect($this->shared[$alias]);
if ($shared instanceof RawArgumentInterface) {
return $shared->getValue();
}
return $shared;
}
if (array_key_exists($alias, $this->sharedDefinitions)) {
$shared = $this->inflectors->inflect($this->sharedDefinitions[$alias]->build());
$this->shared[$alias] = $shared;
return $shared;
}
if (array_key_exists($alias, $this->definitions)) {
return $this->inflectors->inflect(
$this->definitions[$alias]->build($args)
);
}
throw new NotFoundException(
sprintf('Alias (%s) is not being managed by the container', $alias)
);
}
}
<?php
namespace League\Container\Argument;
class RawArgument implements RawArgumentInterface
{
/**
* @var mixed
*/
protected $value;
/**
* {@inheritdoc}
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* {@inheritdoc}
*/
public function getValue()
{
return $this->value;
}
}
<?php
namespace League\Container\Argument;
use League\Container\Exception\NotFoundException;
use League\Container\ReflectionContainer;
use ReflectionFunctionAbstract;
use ReflectionParameter;
trait ArgumentResolverTrait
{
/**
* {@inheritdoc}
*/
public function resolveArguments(array $arguments)
{
foreach ($arguments as &$arg) {
if ($arg instanceof RawArgumentInterface) {
$arg = $arg->getValue();
continue;
}
if (! is_string($arg)) {
continue;
}
$container = $this->getContainer();
if (is_null($container) && $this instanceof ReflectionContainer) {
$container = $this;
}
if (! is_null($container) && $container->has($arg)) {
$arg = $container->get($arg);
if ($arg instanceof RawArgumentInterface) {
$arg = $arg->getValue();
}
continue;
}
}
return $arguments;
}
/**
* {@inheritdoc}
*/
public function reflectArguments(ReflectionFunctionAbstract $method, array $args = [])
{
$arguments = array_map(function (ReflectionParameter $param) use ($method, $args) {
$name = $param->getName();
$class = $param->getClass();
if (array_key_exists($name, $args)) {
return $args[$name];
}
if (! is_null($class)) {
return $class->getName();
}
if ($param->isDefaultValueAvailable()) {
return $param->getDefaultValue();
}
throw new NotFoundException(sprintf(
'Unable to resolve a value for parameter (%s) in the function/method (%s)',
$name,
$method->getName()
));
}, $method->getParameters());
return $this->resolveArguments($arguments);
}
/**
* @return \League\Container\ContainerInterface
*/
abstract public function getContainer();
}
<?php
namespace League\Container\Argument;
use League\Container\ImmutableContainerAwareInterface;
use ReflectionFunctionAbstract;
interface ArgumentResolverInterface extends ImmutableContainerAwareInterface
{
/**
* Resolve an array of arguments to their concrete implementations.
*
* @param array $arguments
* @return array
*/
public function resolveArguments(array $arguments);
/**
* Resolves the correct arguments to be passed to a method.
*
* @param \ReflectionFunctionAbstract $method
* @param array $args
* @return array
*/
public function reflectArguments(ReflectionFunctionAbstract $method, array $args = []);
}
<?php
namespace League\Container\Argument;
interface RawArgumentInterface
{
/**
* Return the value of the raw argument.
*
* @return mixed
*/
public function getValue();
}
<?php
namespace League\Container;
use Interop\Container\ContainerInterface as InteropContainerInterface;
interface ImmutableContainerInterface extends InteropContainerInterface
{
}
<?php
namespace League\Container;
use Interop\Container\ContainerInterface as InteropContainerInterface;
interface ImmutableContainerAwareInterface
{
/**
* Set a container
*
* @param \Interop\Container\ContainerInterface $container
*/
public function setContainer(InteropContainerInterface $container);
/**
* Get the container
*
* @return \League\Container\ImmutableContainerInterface
*/
public function getContainer();
}
<?php
namespace League\Container\Inflector;
use League\Container\ImmutableContainerAwareTrait;
class InflectorAggregate implements InflectorAggregateInterface
{
use ImmutableContainerAwareTrait;
/**
* @var array
*/
protected $inflectors = [];
/**
* {@inheritdoc}
*/
public function add($type, callable $callback = null)
{
if (is_null($callback)) {
$inflector = new Inflector;
$this->inflectors[$type] = $inflector;
return $inflector;
}
$this->inflectors[$type] = $callback;
}
/**
* {@inheritdoc}
*/
public function inflect($object)
{
foreach ($this->inflectors as $type => $inflector) {
if (! $object instanceof $type) {
continue;
}
if ($inflector instanceof Inflector) {
$inflector->setContainer($this->getContainer());
$inflector->inflect($object);
continue;
}
// must be dealing with a callable as the inflector
call_user_func_array($inflector, [$object]);
}
return $object;
}
}
<?php
namespace League\Container\Inflector;
use League\Container\ImmutableContainerAwareInterface;
interface InflectorAggregateInterface extends ImmutableContainerAwareInterface
{
/**
* Add an inflector to the aggregate.
*
* @param string $type
* @param callable $callback
* @return \League\Container\Inflector\Inflector
*/
public function add($type, callable $callback = null);
/**
* Applies all inflectors to an object.
*
* @param object $object
* @return object
*/
public function inflect($object);
}
<?php
namespace League\Container\Inflector;
use League\Container\ImmutableContainerAwareTrait;
use League\Container\Argument\ArgumentResolverInterface;
use League\Container\Argument\ArgumentResolverTrait;
class Inflector implements ArgumentResolverInterface
{
use ArgumentResolverTrait;
use ImmutableContainerAwareTrait;
/**
* @var array
*/
protected $methods = [];
/**
* @var array
*/
protected $properties = [];
/**
* Defines a method to be invoked on the subject object.
*
* @param string $name
* @param array $args
* @return $this
*/
public function invokeMethod($name, array $args)
{
$this->methods[$name] = $args;
return $this;
}
/**
* Defines multiple methods to be invoked on the subject object.
*
* @param array $methods
* @return $this
*/
public function invokeMethods(array $methods)
{
foreach ($methods as $name => $args) {
$this->invokeMethod($name, $args);
}
return $this;
}
/**
* Defines a property to be set on the subject object.
*
* @param string $property
* @param mixed $value
* @return $this
*/
public function setProperty($property, $value)
{
$this->properties[$property] = $value;
return $this;
}
/**
* Defines multiple properties to be set on the subject object.
*
* @param array $properties
* @return $this
*/
public function setProperties(array $properties)
{
foreach ($properties as $property => $value) {
$this->setProperty($property, $value);
}
return $this;
}
/**
* Apply inflections to an object.
*
* @param object $object
* @return void
*/
public function inflect($object)
{
$properties = $this->resolveArguments(array_values($this->properties));
$properties = array_combine(array_keys($this->properties), $properties);
foreach ($properties as $property => $value) {
$object->{$property} = $value;
}
foreach ($this->methods as $method => $args) {
$args = $this->resolveArguments($args);
call_user_func_array([$object, $method], $args);
}
}
}
<?php
namespace League\Container\ServiceProvider;
abstract class AbstractSignatureServiceProvider
extends AbstractServiceProvider
implements SignatureServiceProviderInterface
{
/**
* @var string
*/
protected $signature;
/**
* {@inheritdoc}
*/
public function withSignature($signature)
{
$this->signature = $signature;
return $this;
}
/**
* {@inheritdoc}
*/
public function getSignature()
{
return (is_null($this->signature)) ? get_class($this) : $this->signature;
}
}
<?php
namespace League\Container\ServiceProvider;
use League\Container\ContainerAwareInterface;
interface ServiceProviderAggregateInterface extends ContainerAwareInterface
{
/**
* Add a service provider to the aggregate.
*
* @param string|\League\Container\ServiceProvider\ServiceProviderInterface $provider
* @return $this
*/
public function add($provider);
/**
* Determines whether a service is provided by the aggregate.
*
* @param string $service
* @return boolean
*/
public function provides($service);
/**
* Invokes the register method of a provider that provides a specific service.
*
* @param string $service
* @return void
*/
public function register($service);
}
<?php
namespace League\Container\ServiceProvider;
use League\Container\ContainerAwareTrait;
abstract class AbstractServiceProvider implements ServiceProviderInterface
{
use ContainerAwareTrait;
/**
* @var array
*/
protected $provides = [];
/**
* {@inheritdoc}
*/
public function provides($alias = null)
{
if (! is_null($alias)) {
return (in_array($alias, $this->provides));
}
return $this->provides;
}
}
<?php
namespace League\Container\ServiceProvider;
use League\Container\ContainerAwareInterface;
use League\Container\ContainerAwareTrait;
class ServiceProviderAggregate implements ServiceProviderAggregateInterface
{
use ContainerAwareTrait;
/**
* @var array
*/
protected $providers = [];
/**
* @var array
*/
protected $registered = [];
/**
* {@inheritdoc}
*/
public function add($provider)
{
if (is_string($provider) && class_exists($provider)) {
$provider = new $provider;
}
if ($provider instanceof ContainerAwareInterface) {
$provider->setContainer($this->getContainer());
}
if ($provider instanceof BootableServiceProviderInterface) {
$provider->boot();
}
if ($provider instanceof ServiceProviderInterface) {
foreach ($provider->provides() as $service) {
$this->providers[$service] = $provider;
}
return $this;
}
throw new \InvalidArgumentException(
'A service provider must be a fully qualified class name or instance ' .
'of (\League\Container\ServiceProvider\ServiceProviderInterface)'
);
}
/**
* {@inheritdoc}
*/
public function provides($service)
{
return array_key_exists($service, $this->providers);
}
/**
* {@inheritdoc}
*/
public function register($service)
{
if (! array_key_exists($service, $this->providers)) {
throw new \InvalidArgumentException(
sprintf('(%s) is not provided by a service provider', $service)
);
}
$provider = $this->providers[$service];
$signature = get_class($provider);
if ($provider instanceof SignatureServiceProviderInterface) {
$signature = $provider->getSignature();
}
// ensure that the provider hasn't already been invoked by any other service request
if (in_array($signature, $this->registered)) {
return;
}
$provider->register();
$this->registered[] = $signature;
}
}
<?php
namespace League\Container\ServiceProvider;
interface SignatureServiceProviderInterface
{
/**
* Set a custom signature for the service provider. This enables
* registering the same service provider multiple times.
*
* @param string $signature
* @return self
*/
public function withSignature($signature);
/**
* The signature of the service provider uniquely identifies it, so
* that we can quickly determine if it has already been registered.
* Defaults to get_class($provider).
*
* @return string
*/
public function getSignature();
}
<?php
namespace League\Container\ServiceProvider;
interface BootableServiceProviderInterface extends ServiceProviderInterface
{
/**
* Method will be invoked on registration of a service provider implementing
* this interface. Provides ability for eager loading of Service Providers.
*
* @return void
*/
public function boot();
}
<?php
namespace League\Container\ServiceProvider;
use League\Container\ContainerAwareInterface;
interface ServiceProviderInterface extends ContainerAwareInterface
{
/**
* Returns a boolean if checking whether this provider provides a specific
* service or returns an array of provided services if no argument passed.
*
* @param string $service
* @return boolean|array
*/
public function provides($service = null);
/**
* Use the register method to register items with the container via the
* protected $this->container property or the `getContainer` method
* from the ContainerAwareTrait.
*
* @return void
*/
public function register();
}
<?php
namespace League\Container;
interface ContainerInterface extends ImmutableContainerInterface
{
/**
* Add an item to the container.
*
* @param string $alias
* @param mixed|null $concrete
* @param boolean $share
* @return \League\Container\Definition\DefinitionInterface
*/
public function add($alias, $concrete = null, $share = false);
/**
* Convenience method to add an item to the container as a shared item.
*
* @param string $alias
* @param mixed|null $concrete
* @return \League\Container\Definition\DefinitionInterface
*/
public function share($alias, $concrete = null);
/**
* Add a service provider to the container.
*
* @param string|\League\Container\ServiceProvider\ServiceProviderInterface $provider
* @return void
*/
public function addServiceProvider($provider);
/**
* Returns a definition of an item to be extended.
*
* @param string $alias
* @return \League\Container\Definition\DefinitionInterface
*/
public function extend($alias);
/**
* Allows for manipulation of specific types on resolution.
*
* @param string $type
* @param callable|null $callback
* @return \League\Container\Inflector\Inflector|void
*/
public function inflector($type, callable $callback = null);
/**
* Invoke a callable via the container.
*
* @param callable $callable
* @param array $args
* @return mixed
*/
public function call(callable $callable, array $args = []);
}
<?php
namespace League\Container\Definition;
class CallableDefinition extends AbstractDefinition
{
/**
* {@inheritdoc}
*/
public function build(array $args = [])
{
$args = (empty($args)) ? $this->arguments : $args;
$resolved = $this->resolveArguments($args);
if (is_array($this->concrete) && is_string($this->concrete[0])) {
$this->concrete[0] = ($this->getContainer()->has($this->concrete[0]))
? $this->getContainer()->get($this->concrete[0])
: $this->concrete[0];
}
return call_user_func_array($this->concrete, $resolved);
}
}
<?php
namespace League\Container\Definition;
use League\Container\ImmutableContainerAwareInterface;
interface DefinitionFactoryInterface extends ImmutableContainerAwareInterface
{
/**
* Return a definition based on type of concrete.
*
* @param string $alias
* @param mixed $concrete
* @return mixed
*/
public function getDefinition($alias, $concrete);
}
<?php
namespace League\Container\Definition;
use League\Container\ImmutableContainerAwareTrait;
class DefinitionFactory implements DefinitionFactoryInterface
{
use ImmutableContainerAwareTrait;
/**
* {@inheritdoc}
*/
public function getDefinition($alias, $concrete)
{
if (is_callable($concrete)) {
return (new CallableDefinition($alias, $concrete))->setContainer($this->getContainer());
}
if (is_string($concrete) && class_exists($concrete)) {
return (new ClassDefinition($alias, $concrete))->setContainer($this->getContainer());
}
// if the item is not definable we just return the value to be stored
// in the container as an arbitrary value/instance
return $concrete;
}
}
<?php
namespace League\Container\Definition;
use ReflectionClass;
class ClassDefinition extends AbstractDefinition implements ClassDefinitionInterface
{
/**
* @var array
*/
protected $methods = [];
/**
* {@inheritdoc}
*/
public function withMethodCall($method, array $args = [])
{
$this->methods[] = [
'method' => $method,
'arguments' => $args
];
return $this;
}
/**
* {@inheritdoc}
*/
public function withMethodCalls(array $methods = [])
{
foreach ($methods as $method => $args) {
$this->withMethodCall($method, $args);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function build(array $args = [])
{
$args = (empty($args)) ? $this->arguments : $args;
$resolved = $this->resolveArguments($args);
$reflection = new ReflectionClass($this->concrete);
$instance = $reflection->newInstanceArgs($resolved);
return $this->invokeMethods($instance);
}
/**
* Invoke methods on resolved instance.
*
* @param object $instance
* @return object
*/
protected function invokeMethods($instance)
{
foreach ($this->methods as $method) {
$args = $this->resolveArguments($method['arguments']);
call_user_func_array([$instance, $method['method']], $args);
}
return $instance;
}
}
<?php
namespace League\Container\Definition;
interface ClassDefinitionInterface extends DefinitionInterface
{
/**
* Add a method to be invoked
*
* @param string $method
* @param array $args
* @return $this
*/
public function withMethodCall($method, array $args = []);
/**
* Add multiple methods to be invoked
*
* @param array $methods
* @return $this
*/
public function withMethodCalls(array $methods = []);
}
<?php
namespace League\Container\Definition;
interface DefinitionInterface
{
/**
* Handle instantiation and manipulation of value and return.
*
* @param array $args
* @return mixed
*/
public function build(array $args = []);
/**
* Add an argument to be injected.
*
* @param mixed $arg
* @return $this
*/
public function withArgument($arg);
/**
* Add multiple arguments to be injected.
*
* @param array $args
* @return $this
*/
public function withArguments(array $args);
}
<?php
namespace League\Container\Definition;
use League\Container\Argument\ArgumentResolverInterface;
use League\Container\Argument\ArgumentResolverTrait;
use League\Container\ImmutableContainerAwareTrait;
abstract class AbstractDefinition implements ArgumentResolverInterface, DefinitionInterface
{
use ArgumentResolverTrait;
use ImmutableContainerAwareTrait;
/**
* @var string
*/
protected $alias;
/**
* @var mixed
*/
protected $concrete;
/**
* @var array
*/
protected $arguments = [];
/**
* Constructor.
*
* @param string $alias
* @param mixed $concrete
*/
public function __construct($alias, $concrete)
{
$this->alias = $alias;
$this->concrete = $concrete;
}
/**
* {@inheritdoc}
*/
public function withArgument($arg)
{
$this->arguments[] = $arg;
return $this;
}
/**
* {@inheritdoc}
*/
public function withArguments(array $args)
{
foreach ($args as $arg) {
$this->withArgument($arg);
}
return $this;
}
}
<?php
namespace League\Container;
use Interop\Container\ContainerInterface as InteropContainerInterface;
trait ImmutableContainerAwareTrait
{
/**
* @var \Interop\Container\ContainerInterface
*/
protected $container;
/**
* Set a container.
*
* @param \Interop\Container\ContainerInterface $container
* @return $this
*/
public function setContainer(InteropContainerInterface $container)
{
$this->container = $container;
return $this;
}
/**
* Get the container.
*
* @return \League\Container\ImmutableContainerInterface
*/
public function getContainer()
{
return $this->container;
}
}
<?php
namespace League\Container;
use League\Container\Argument\ArgumentResolverInterface;
use League\Container\Argument\ArgumentResolverTrait;
use League\Container\Exception\NotFoundException;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
class ReflectionContainer implements
ArgumentResolverInterface,
ImmutableContainerInterface
{
use ArgumentResolverTrait;
use ImmutableContainerAwareTrait;
/**
* {@inheritdoc}
*/
public function get($alias, array $args = [])
{
if (! $this->has($alias)) {
throw new NotFoundException(
sprintf('Alias (%s) is not an existing class and therefore cannot be resolved', $alias)
);
}
$reflector = new ReflectionClass($alias);
$construct = $reflector->getConstructor();
if ($construct === null) {
return new $alias;
}
return $reflector->newInstanceArgs(
$this->reflectArguments($construct, $args)
);
}
/**
* {@inheritdoc}
*/
public function has($alias)
{
return class_exists($alias);
}
/**
* Invoke a callable via the container.
*
* @param callable $callable
* @param array $args
* @return mixed
*/
public function call(callable $callable, array $args = [])
{
if (is_string($callable) && strpos($callable, '::') !== false) {
$callable = explode('::', $callable);
}
if (is_array($callable)) {
if (is_string($callable[0])) {
$callable[0] = $this->getContainer()->get($callable[0]);
}
$reflection = new ReflectionMethod($callable[0], $callable[1]);
if ($reflection->isStatic()) {
$callable[0] = null;
}
return $reflection->invokeArgs($callable[0], $this->reflectArguments($reflection, $args));
}
if (is_object($callable)) {
$reflection = new ReflectionMethod($callable, '__invoke');
return $reflection->invokeArgs($callable, $this->reflectArguments($reflection, $args));
}
$reflection = new ReflectionFunction($callable);
return $reflection->invokeArgs($this->reflectArguments($reflection, $args));
}
}
<?php
namespace League\Container\Exception;
use Interop\Container\Exception\NotFoundException as NotFoundExceptionInterface;
use InvalidArgumentException;
class NotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface
{
}
<?php
namespace League\Container;
interface ContainerAwareInterface
{
/**
* Set a container
*
* @param \League\Container\ContainerInterface $container
*/
public function setContainer(ContainerInterface $container);
/**
* Get the container
*
* @return \League\Container\ContainerInterface
*/
public function getContainer();
}
<?php
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData;
interface DataInterface
{
/**
* Append a value to a key (assumes key refers to an array value)
*
* @param string $key
* @param mixed $value
*/
public function append($key, $value = null);
/**
* Set a value for a key
*
* @param string $key
* @param mixed $value
*/
public function set($key, $value = null);
/**
* Remove a key
*
* @param string $key
*/
public function remove($key);
/**
* Get the raw value for a key
*
* @param string $key
* @param mixed $default
*
* @return mixed
*/
public function get($key, $default = null);
/**
* Check if the key exists
*
* @param string $key
*
* @return bool
*/
public function has($key);
/**
* Get a data instance for a key
*
* @param string $key
*
* @return DataInterface
*/
public function getData($key);
/**
* Import data into existing data
*
* @param array $data
* @param bool $clobber
*/
public function import(array $data, $clobber = true);
/**
* Import data from an external data into existing data
*
* @param DataInterface $data
* @param bool $clobber
*/
public function importData(DataInterface $data, $clobber = true);
/**
* Export data as raw data
*
* @return array
*/
public function export();
}
<?php
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData;
class Data implements DataInterface
{
/**
* Internal representation of data data
*
* @var array
*/
protected $data;
/**
* Constructor
*
* @param array|null $data
*/
public function __construct(array $data = null)
{
$this->data = $data ?: array();
}
/**
* {@inheritdoc}
*/
public function append($key, $value = null)
{
if (0 == strlen($key)) {
throw new \RuntimeException("Key cannot be an empty string");
}
$currentValue =& $this->data;
$keyPath = explode('.', $key);
if (1 == count($keyPath)) {
if (!isset($currentValue[$key])) {
$currentValue[$key] = array();
}
if (!is_array($currentValue[$key])) {
// Promote this key to an array.
// TODO: Is this really what we want to do?
$currentValue[$key] = array($currentValue[$key]);
}
$currentValue[$key][] = $value;
return;
}
$endKey = array_pop($keyPath);
for ( $i = 0; $i < count($keyPath); $i++ ) {
$currentKey =& $keyPath[$i];
if ( ! isset($currentValue[$currentKey]) ) {
$currentValue[$currentKey] = array();
}
$currentValue =& $currentValue[$currentKey];
}
if (!isset($currentValue[$endKey])) {
$currentValue[$endKey] = array();
}
if (!is_array($currentValue[$endKey])) {
$currentValue[$endKey] = array($currentValue[$endKey]);
}
// Promote this key to an array.
// TODO: Is this really what we want to do?
$currentValue[$endKey][] = $value;
}
/**
* {@inheritdoc}
*/
public function set($key, $value = null)
{
if (0 == strlen($key)) {
throw new \RuntimeException("Key cannot be an empty string");
}
$currentValue =& $this->data;
$keyPath = explode('.', $key);
if (1 == count($keyPath)) {
$currentValue[$key] = $value;
return;
}
$endKey = array_pop($keyPath);
for ( $i = 0; $i < count($keyPath); $i++ ) {
$currentKey =& $keyPath[$i];
if (!isset($currentValue[$currentKey])) {
$currentValue[$currentKey] = array();
}
if (!is_array($currentValue[$currentKey])) {
throw new \RuntimeException("Key path at $currentKey of $key cannot be indexed into (is not an array)");
}
$currentValue =& $currentValue[$currentKey];
}
$currentValue[$endKey] = $value;
}
/**
* {@inheritdoc}
*/
public function remove($key)
{
if (0 == strlen($key)) {
throw new \RuntimeException("Key cannot be an empty string");
}
$currentValue =& $this->data;
$keyPath = explode('.', $key);
if (1 == count($keyPath)) {
unset($currentValue[$key]);
return;
}
$endKey = array_pop($keyPath);
for ( $i = 0; $i < count($keyPath); $i++ ) {
$currentKey =& $keyPath[$i];
if (!isset($currentValue[$currentKey])) {
return;
}
$currentValue =& $currentValue[$currentKey];
}
unset($currentValue[$endKey]);
}
/**
* {@inheritdoc}
*/
public function get($key, $default = null)
{
$currentValue = $this->data;
$keyPath = explode('.', $key);
for ( $i = 0; $i < count($keyPath); $i++ ) {
$currentKey = $keyPath[$i];
if (!isset($currentValue[$currentKey]) ) {
return $default;
}
if (!is_array($currentValue)) {
return $default;
}
$currentValue = $currentValue[$currentKey];
}
return $currentValue === null ? $default : $currentValue;
}
/**
* {@inheritdoc}
*/
public function has($key)
{
$currentValue = &$this->data;
$keyPath = explode('.', $key);
for ( $i = 0; $i < count($keyPath); $i++ ) {
$currentKey = $keyPath[$i];
if (
!is_array($currentValue) ||
!array_key_exists($currentKey, $currentValue)
) {
return false;
}
$currentValue = &$currentValue[$currentKey];
}
return true;
}
/**
* {@inheritdoc}
*/
public function getData($key)
{
$value = $this->get($key);
if (is_array($value) && Util::isAssoc($value)) {
return new Data($value);
}
throw new \RuntimeException("Value at '$key' could not be represented as a DataInterface");
}
/**
* {@inheritdoc}
*/
public function import(array $data, $clobber = true)
{
$this->data = Util::mergeAssocArray($this->data, $data, $clobber);
}
/**
* {@inheritdoc}
*/
public function importData(DataInterface $data, $clobber = true)
{
$this->import($data->export(), $clobber);
}
/**
* {@inheritdoc}
*/
public function export()
{
return $this->data;
}
}
<?php
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData;
class Util
{
/**
* Test if array is an associative array
*
* Note that this function will return true if an array is empty. Meaning
* empty arrays will be treated as if they are associative arrays.
*
* @param array $arr
*
* @return boolean
*/
public static function isAssoc(array $arr)
{
return (is_array($arr) && (!count($arr) || count(array_filter(array_keys($arr),'is_string')) == count($arr)));
}
/**
* Merge contents from one associtative array to another
*
* @param array $to
* @param array $from
* @param bool $clobber
*/
public static function mergeAssocArray($to, $from, $clobber = true)
{
if ( is_array($from) ) {
foreach ($from as $k => $v) {
if (!isset($to[$k])) {
$to[$k] = $v;
} else {
$to[$k] = self::mergeAssocArray($to[$k], $v, $clobber);
}
}
return $to;
}
return $clobber ? $from : $to;
}
}
<?php
namespace Grasmash\Expander;
interface StringifierInterface
{
/**
* Converts array to string.
*
* @param array $array
* The array to convert.
*
* @return string
* The resultant string.
*/
public static function stringifyArray(array $array);
}
<?php
namespace Grasmash\Expander;
/**
* Class Stringifier
* @package Grasmash\Expander
*/
class Stringifier implements StringifierInterface
{
/**
* Converts array to string.
*
* @param array $array
* The array to convert.
*
* @return string
* The resultant string.
*/
public static function stringifyArray(array $array)
{
return implode(',', $array);
}
}
<?php
namespace Grasmash\Expander;
use Dflydev\DotAccessData\Data;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\NullLogger;
/**
* Class Expander
* @package Grasmash\Expander
*/
class Expander implements LoggerAwareInterface
{
/**
* @var \Grasmash\Expander\StringifierInterface
*/
protected $stringifier;
/**
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
public function __construct()
{
$this->setLogger(new NullLogger());
$this->setStringifier(new Stringifier());
}
/**
* @return \Grasmash\Expander\StringifierInterface
*/
public function getStringifier()
{
return $this->stringifier;
}
/**
* @param \Grasmash\Expander\StringifierInterface $stringifier
*/
public function setStringifier(\Grasmash\Expander\StringifierInterface $stringifier)
{
$this->stringifier = $stringifier;
}
/**
* @return \Psr\Log\LoggerInterface
*/
public function getLogger()
{
return $this->logger;
}
/**
* @param \Psr\Log\LoggerInterface $logger
*/
public function setLogger(\Psr\Log\LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* Expands property placeholders in an array.
*
* Placeholders should formatted as ${parent.child}.
*
* @param array $array
* An array containing properties to expand.
*
* @return array
* The modified array in which placeholders have been replaced with
* values.
*/
public function expandArrayProperties($array, $reference_array = [])
{
$data = new Data($array);
if ($reference_array) {
$reference_data = new Data($reference_array);
$this->doExpandArrayProperties($data, $array, '', $reference_data);
} else {
$this->doExpandArrayProperties($data, $array);
}
return $data->export();
}
/**
* Performs the actual property expansion.
*
* @param Data $data
* A data object, containing the $array.
* @param array $array
* The original, unmodified array.
* @param string $parent_keys
* The parent keys of the current key in dot notation. This is used to
* track the absolute path to the current key in recursive cases.
* @param Data|null $reference_data
* A reference data object. This is not operated upon but is used as a
* reference to provide supplemental values for property expansion.
*/
protected function doExpandArrayProperties(
$data,
$array,
$parent_keys = '',
$reference_data = null
) {
foreach ($array as $key => $value) {
// Boundary condition(s).
if (is_null($value) || is_bool($value)) {
continue;
}
// Recursive case.
if (is_array($value)) {
$this->doExpandArrayProperties($data, $value, $parent_keys . "$key.", $reference_data);
} // Base case.
else {
$this->expandStringProperties($data, $parent_keys, $reference_data, $value, $key);
}
}
}
/**
* Expand a single property.
*
* @param Data $data
* A data object, containing the $array.
* @param string $parent_keys
* The parent keys of the current key in dot notation. This is used to
* track the absolute path to the current key in recursive cases.
* @param Data|null $reference_data
* A reference data object. This is not operated upon but is used as a
* reference to provide supplemental values for property expansion.
* @param string $value
* The unexpanded property value.
* @param string $key
* The immediate key of the property.
*
* @return mixed
*/
protected function expandStringProperties(
$data,
$parent_keys,
$reference_data,
$value,
$key
) {
// We loop through all placeholders in a given string.
// E.g., '${placeholder1} ${placeholder2}' requires two replacements.
while (strpos($value, '${') !== false) {
$original_value = $value;
$value = preg_replace_callback(
'/\$\{([^\$}]+)\}/',
function ($matches) use ($data, $reference_data) {
return $this->expandStringPropertiesCallback(
$matches,
$data,
$reference_data
);
},
$value
);
// If no replacement occurred at all, break to prevent
// infinite loop.
if ($original_value == $value) {
break;
}
// Set value on $data object.
if ($parent_keys) {
$full_key = $parent_keys . "$key";
} else {
$full_key = $key;
}
$data->set($full_key, $value);
}
return $value;
}
/**
* Expansion callback used by preg_replace_callback() in expandProperty().
*
* @param array $matches
* An array of matches created by preg_replace_callback().
* @param Data $data
* A data object containing the complete array being operated upon.
* @param Data|null $reference_data
* A reference data object. This is not operated upon but is used as a
* reference to provide supplemental values for property expansion.
*
* @return mixed
*/
public function expandStringPropertiesCallback(
$matches,
$data,
$reference_data = null
) {
$property_name = $matches[1];
$unexpanded_value = $matches[0];
// Use only values within the subject array's data.
if (!$reference_data) {
return $this->expandProperty($property_name, $unexpanded_value, $data);
} // Search both the subject array's data and the reference data for a value.
else {
return $this->expandPropertyWithReferenceData(
$property_name,
$unexpanded_value,
$data,
$reference_data
);
}
}
/**
* Searches both the subject data and the reference data for value.
*
* @param string $property_name
* The name of the value for which to search.
* @param string $unexpanded_value
* The original, unexpanded value, containing the placeholder.
* @param Data $data
* A data object containing the complete array being operated upon.
* @param Data|null $reference_data
* A reference data object. This is not operated upon but is used as a
* reference to provide supplemental values for property expansion.
*
* @return string
* The expanded string.
*/
public function expandPropertyWithReferenceData(
$property_name,
$unexpanded_value,
$data,
$reference_data
) {
$expanded_value = $this->expandProperty(
$property_name,
$unexpanded_value,
$data
);
// If the string was not changed using the subject data, try using
// the reference data.
if ($expanded_value == $unexpanded_value) {
$expanded_value = $this->expandProperty(
$property_name,
$unexpanded_value,
$reference_data
);
}
return $expanded_value;
}
/**
* Searches a data object for a value.
*
* @param string $property_name
* The name of the value for which to search.
* @param string $unexpanded_value
* The original, unexpanded value, containing the placeholder.
* @param Data $data
* A data object containing possible replacement values.
*
* @return mixed
*/
public function expandProperty($property_name, $unexpanded_value, $data)
{
if (strpos($property_name, "env.") === 0 &&
!$data->has($property_name)) {
$env_key = substr($property_name, 4);
if (getenv($env_key)) {
$data->set($property_name, getenv($env_key));
}
}
if (!$data->has($property_name)) {
$this->log("Property \${'$property_name'} could not be expanded.");
return $unexpanded_value;
} else {
$expanded_value = $data->get($property_name);
if (is_array($expanded_value)) {
$expanded_value = $this->getStringifier()->stringifyArray($expanded_value);
return $expanded_value;
}
$this->log("Expanding property \${'$property_name'} => $expanded_value.");
return $expanded_value;
}
}
/**
* Logs a message using the logger.
*
* @param string $message
* The message to log.
*/
public function log($message)
{
if ($this->getLogger()) {
$this->getLogger()->debug($message);
}
}
}
<?php
namespace Grasmash\YamlExpander;
use Dflydev\DotAccessData\Data;
use Symfony\Component\Yaml\Yaml;
/**
* Class Expander
* @package Grasmash\YamlExpander
*/
class Expander
{
/**
* Parses a YAML string and expands property placeholders.
*
* Placeholders should formatted as ${parent.child}.
*
* @param string $yaml_string
* A string of YAML.
* @param array $reference_array
* Optional. An array of reference values. This is not operated upon but is used as a
* reference to provide supplemental values for property expansion.
*
* @return array
* The modified array in which placeholders have been replaced with
* values.
*/
public static function parse($yaml_string, $reference_array = [])
{
$array = Yaml::parse($yaml_string);
return self::expandArrayProperties($array, $reference_array);
}
/**
* Expands property placeholders in an array.
*
* Placeholders should formatted as ${parent.child}.
*
* @param array $array
* An array containing properties to expand.
*
* @return array
* The modified array in which placeholders have been replaced with
* values.
*/
public static function expandArrayProperties($array, $reference_array = [])
{
$data = new Data($array);
if ($reference_array) {
$reference_data = new Data($reference_array);
self::doExpandArrayProperties($data, $array, '', $reference_data);
} else {
self::doExpandArrayProperties($data, $array);
}
return $data->export();
}
/**
* Performs the actual property expansion.
*
* @param Data $data
* A data object, containing the $array.
* @param array $array
* The original, unmodified array.
* @param string $parent_keys
* The parent keys of the current key in dot notation. This is used to
* track the absolute path to the current key in recursive cases.
* @param Data|null $reference_data
* A reference data object. This is not operated upon but is used as a
* reference to provide supplemental values for property expansion.
*/
protected static function doExpandArrayProperties(
$data,
$array,
$parent_keys = '',
$reference_data = null
) {
foreach ($array as $key => $value) {
// Boundary condition(s).
if (is_null($value) || is_bool($value)) {
continue;
}
// Recursive case.
if (is_array($value)) {
self::doExpandArrayProperties($data, $value, $parent_keys . "$key.", $reference_data);
} // Base case.
else {
self::expandStringProperties($data, $parent_keys, $reference_data, $value, $key);
}
}
}
/**
* Expand a single property.
*
* @param Data $data
* A data object, containing the $array.
* @param string $parent_keys
* The parent keys of the current key in dot notation. This is used to
* track the absolute path to the current key in recursive cases.
* @param Data|null $reference_data
* A reference data object. This is not operated upon but is used as a
* reference to provide supplemental values for property expansion.
* @param string $value
* The unexpanded property value.
* @param string $key
* The immediate key of the property.
*
* @return mixed
*/
protected static function expandStringProperties(
$data,
$parent_keys,
$reference_data,
$value,
$key
) {
// We loop through all placeholders in a given string.
// E.g., '${placeholder1} ${placeholder2}' requires two replacements.
while (strpos($value, '${') !== false) {
$original_value = $value;
$value = preg_replace_callback(
'/\$\{([^\$}]+)\}/',
function ($matches) use ($data, $reference_data) {
return self::expandStringPropertiesCallback(
$matches,
$data,
$reference_data
);
},
$value
);
// If no replacement occurred at all, break to prevent
// infinite loop.
if ($original_value == $value) {
break;
}
// Set value on $data object.
if ($parent_keys) {
$full_key = $parent_keys . "$key";
} else {
$full_key = $key;
}
$data->set($full_key, $value);
}
return $value;
}
/**
* Expansion callback used by preg_replace_callback() in expandProperty().
*
* @param array $matches
* An array of matches created by preg_replace_callback().
* @param Data $data
* A data object containing the complete array being operated upon.
* @param Data|null $reference_data
* A reference data object. This is not operated upon but is used as a
* reference to provide supplemental values for property expansion.
*
* @return mixed
*/
public static function expandStringPropertiesCallback(
$matches,
$data,
$reference_data = null
) {
$property_name = $matches[1];
$unexpanded_value = $matches[0];
// Use only values within the subject array's data.
if (!$reference_data) {
return self::expandProperty($property_name, $unexpanded_value, $data);
} // Search both the subject array's data and the reference data for a value.
else {
return self::expandPropertyWithReferenceData(
$property_name,
$unexpanded_value,
$data,
$reference_data
);
}
}
/**
* Searches both the subject data and the reference data for value.
*
* @param string $property_name
* The name of the value for which to search.
* @param string $unexpanded_value
* The original, unexpanded value, containing the placeholder.
* @param Data $data
* A data object containing the complete array being operated upon.
* @param Data|null $reference_data
* A reference data object. This is not operated upon but is used as a
* reference to provide supplemental values for property expansion.
*
* @return string
* The expanded string.
*/
public static function expandPropertyWithReferenceData(
$property_name,
$unexpanded_value,
$data,
$reference_data
) {
$expanded_value = self::expandProperty(
$property_name,
$unexpanded_value,
$data
);
// If the string was not changed using the subject data, try using
// the reference data.
if ($expanded_value == $unexpanded_value) {
$expanded_value = self::expandProperty(
$property_name,
$unexpanded_value,
$reference_data
);
}
return $expanded_value;
}
/**
* Searches a data object for a value.
*
* @param string $property_name
* The name of the value for which to search.
* @param string $unexpanded_value
* The original, unexpanded value, containing the placeholder.
* @param Data $data
* A data object containing possible replacement values.
*
* @return mixed
*/
public static function expandProperty($property_name, $unexpanded_value, $data)
{
if (strpos($property_name, "env.") === 0 &&
!$data->has($property_name)) {
$env_key = substr($property_name, 4);
if (getenv($env_key)) {
$data->set($property_name, getenv($env_key));
}
}
if (!$data->has($property_name)) {
self::log("Property \${'$property_name'} could not be expanded.");
return $unexpanded_value;
} else {
$expanded_value = $data->get($property_name);
if (is_array($expanded_value)) {
$expanded_value = Yaml::dump($expanded_value, 0);
return $expanded_value;
}
self::log("Expanding property \${'$property_name'} => $expanded_value.");
return $expanded_value;
}
}
/**
* @param $message
*/
public static function log($message)
{
// print "$message\n";
}
}
<?php
/**
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace Psr\Container;
/**
* No entry was found in the container.
*/
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}
<?php
/**
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace Psr\Container;
/**
* Describes the interface of a container that exposes methods to read its entries.
*/
interface ContainerInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
*
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws ContainerExceptionInterface Error while retrieving the entry.
*
* @return mixed Entry.
*/
public function get($id);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string $id Identifier of the entry to look for.
*
* @return bool
*/
public function has($id);
}
<?php
/**
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace Psr\Container;
/**
* Base interface representing a generic exception in a container.
*/
interface ContainerExceptionInterface
{
}
<?php
namespace Psr\Log;
/**
* Describes a logger-aware instance.
*/
interface LoggerAwareInterface
{
/**
* Sets a logger instance on the object.
*
* @param LoggerInterface $logger
*
* @return void
*/
public function setLogger(LoggerInterface $logger);
}
<?php
namespace Psr\Log;
/**
* Describes log levels.
*/
class LogLevel
{
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
}
<?php
namespace Psr\Log;
/**
* Basic Implementation of LoggerAwareInterface.
*/
trait LoggerAwareTrait
{
/**
* The logger instance.
*
* @var LoggerInterface
*/
protected $logger;
/**
* Sets a logger.
*
* @param LoggerInterface $logger
*/
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
}
<?php
namespace Psr\Log;
class InvalidArgumentException extends \InvalidArgumentException
{
}
<?php
namespace Psr\Log;
/**
* This Logger can be used to avoid conditional log calls.
*
* Logging should always be optional, and if no logger is provided to your
* library creating a NullLogger instance to have something to throw logs at
* is a good way to avoid littering your code with `if ($this->logger) { }`
* blocks.
*/
class NullLogger extends AbstractLogger
{
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
*
* @return void
*
* @throws \Psr\Log\InvalidArgumentException
*/
public function log($level, $message, array $context = array())
{
// noop
}
}
<?php
namespace Psr\Log;
/**
* Describes a logger instance.
*
* The message MUST be a string or object implementing __toString().
*
* The message MAY contain placeholders in the form: {foo} where foo
* will be replaced by the context data in key "foo".
*
* The context array can contain arbitrary data. The only assumption that
* can be made by implementors is that if an Exception instance is given
* to produce a stack trace, it MUST be in a key named "exception".
*
* See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
* for the full interface specification.
*/
interface LoggerInterface
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function emergency($message, array $context = array());
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function alert($message, array $context = array());
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function critical($message, array $context = array());
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function error($message, array $context = array());
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function warning($message, array $context = array());
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function notice($message, array $context = array());
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function info($message, array $context = array());
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function debug($message, array $context = array());
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
*
* @return void
*
* @throws \Psr\Log\InvalidArgumentException
*/
public function log($level, $message, array $context = array());
}
<?php
namespace Psr\Log;
/**
* This is a simple Logger trait that classes unable to extend AbstractLogger
* (because they extend another class, etc) can include.
*
* It simply delegates all log-level-specific methods to the `log` method to
* reduce boilerplate code that a simple Logger that does the same thing with
* messages regardless of the error level has to implement.
*/
trait LoggerTrait
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function emergency($message, array $context = array())
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function alert($message, array $context = array())
{
$this->log(LogLevel::ALERT, $message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function critical($message, array $context = array())
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function error($message, array $context = array())
{
$this->log(LogLevel::ERROR, $message, $context);
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function warning($message, array $context = array())
{
$this->log(LogLevel::WARNING, $message, $context);
}
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function notice($message, array $context = array())
{
$this->log(LogLevel::NOTICE, $message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function info($message, array $context = array())
{
$this->log(LogLevel::INFO, $message, $context);
}
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function debug($message, array $context = array())
{
$this->log(LogLevel::DEBUG, $message, $context);
}
/**
* Logs with an arbitrary level.
*
* @param mixed $level
* @param string $message
* @param array $context
*
* @return void
*
* @throws \Psr\Log\InvalidArgumentException
*/
abstract public function log($level, $message, array $context = array());
}
<?php
namespace Psr\Log;
/**
* This is a simple Logger implementation that other Loggers can inherit from.
*
* It simply delegates all log-level-specific methods to the `log` method to
* reduce boilerplate code that a simple Logger that does the same thing with
* messages regardless of the error level has to implement.
*/
abstract class AbstractLogger implements LoggerInterface
{
/**
* System is unusable.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function emergency($message, array $context = array())
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
/**
* Action must be taken immediately.
*
* Example: Entire website down, database unavailable, etc. This should
* trigger the SMS alerts and wake you up.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function alert($message, array $context = array())
{
$this->log(LogLevel::ALERT, $message, $context);
}
/**
* Critical conditions.
*
* Example: Application component unavailable, unexpected exception.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function critical($message, array $context = array())
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
/**
* Runtime errors that do not require immediate action but should typically
* be logged and monitored.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function error($message, array $context = array())
{
$this->log(LogLevel::ERROR, $message, $context);
}
/**
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things
* that are not necessarily wrong.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function warning($message, array $context = array())
{
$this->log(LogLevel::WARNING, $message, $context);
}
/**
* Normal but significant events.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function notice($message, array $context = array())
{
$this->log(LogLevel::NOTICE, $message, $context);
}
/**
* Interesting events.
*
* Example: User logs in, SQL logs.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function info($message, array $context = array())
{
$this->log(LogLevel::INFO, $message, $context);
}
/**
* Detailed debug information.
*
* @param string $message
* @param array $context
*
* @return void
*/
public function debug($message, array $context = array())
{
$this->log(LogLevel::DEBUG, $message, $context);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Php73;
/**
* @author Gabriel Caruso <carusogabriel34@gmail.com>
* @author Ion Bazan <ion.bazan@gmail.com>
*
* @internal
*/
final class Php73
{
public static $startAt = 1533462603;
/**
* @param bool $asNum
*
* @return array|float|int
*/
public static function hrtime($asNum = false)
{
$ns = microtime(false);
$s = substr($ns, 11) - self::$startAt;
$ns = 1E9 * (float) $ns;
if ($asNum) {
$ns += $s * 1E9;
return \PHP_INT_SIZE === 4 ? $ns : (int) $ns;
}
return array($s, (int) $ns);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class JsonException extends Exception
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Php73 as p;
if (PHP_VERSION_ID < 70300) {
if (!function_exists('is_countable')) {
function is_countable($var) { return is_array($var) || $var instanceof Countable || $var instanceof ResourceBundle || $var instanceof SimpleXmlElement; }
}
if (!function_exists('hrtime')) {
require_once __DIR__.'/Php73.php';
p\Php73::$startAt = (int) microtime(true);
function hrtime($asNum = false) { return p\Php73::hrtime($asNum); }
}
if (!function_exists('array_key_first')) {
function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } }
}
if (!function_exists('array_key_last')) {
function array_key_last(array $array) { end($array); return key($array); }
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Ctype as p;
if (!function_exists('ctype_alnum')) {
function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); }
function ctype_digit($text) { return p\Ctype::ctype_digit($text); }
function ctype_graph($text) { return p\Ctype::ctype_graph($text); }
function ctype_lower($text) { return p\Ctype::ctype_lower($text); }
function ctype_print($text) { return p\Ctype::ctype_print($text); }
function ctype_punct($text) { return p\Ctype::ctype_punct($text); }
function ctype_space($text) { return p\Ctype::ctype_space($text); }
function ctype_upper($text) { return p\Ctype::ctype_upper($text); }
function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); }
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Ctype;
/**
* Ctype implementation through regex.
*
* @internal
*
* @author Gert de Pagter <BackEndTea@gmail.com>
*/
final class Ctype
{
/**
* Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
*
* @see https://php.net/ctype-alnum
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_alnum($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
}
/**
* Returns TRUE if every character in text is a letter, FALSE otherwise.
*
* @see https://php.net/ctype-alpha
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_alpha($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
}
/**
* Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise.
*
* @see https://php.net/ctype-cntrl
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_cntrl($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
}
/**
* Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
*
* @see https://php.net/ctype-digit
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_digit($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
}
/**
* Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise.
*
* @see https://php.net/ctype-graph
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_graph($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
}
/**
* Returns TRUE if every character in text is a lowercase letter.
*
* @see https://php.net/ctype-lower
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_lower($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
}
/**
* Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all.
*
* @see https://php.net/ctype-print
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_print($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
}
/**
* Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise.
*
* @see https://php.net/ctype-punct
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_punct($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
}
/**
* Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters.
*
* @see https://php.net/ctype-space
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_space($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
}
/**
* Returns TRUE if every character in text is an uppercase letter.
*
* @see https://php.net/ctype-upper
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_upper($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
}
/**
* Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise.
*
* @see https://php.net/ctype-xdigit
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_xdigit($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
}
/**
* Converts integers to their char versions according to normal ctype behaviour, if needed.
*
* If an integer between -128 and 255 inclusive is provided,
* it is interpreted as the ASCII value of a single character
* (negative values have 256 added in order to allow characters in the Extended ASCII range).
* Any other integer is interpreted as a string containing the decimal digits of the integer.
*
* @param string|int $int
*
* @return mixed
*/
private static function convert_int_to_char_for_ctype($int)
{
if (!\is_int($int)) {
return $int;
}
if ($int < -128 || $int > 255) {
return (string) $int;
}
if ($int < 0) {
$int += 256;
}
return \chr($int);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
* Listeners are registered on the manager and events are dispatched through the
* manager.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface EventDispatcherInterface extends ContractsEventDispatcherInterface
{
/**
* Adds an event listener that listens on the specified events.
*
* @param string $eventName The event to listen on
* @param callable $listener The listener
* @param int $priority The higher this value, the earlier an event
* listener will be triggered in the chain (defaults to 0)
*/
public function addListener($eventName, $listener, $priority = 0);
/**
* Adds an event subscriber.
*
* The subscriber is asked for all the events it is
* interested in and added as a listener for these events.
*/
public function addSubscriber(EventSubscriberInterface $subscriber);
/**
* Removes an event listener from the specified events.
*
* @param string $eventName The event to remove a listener from
* @param callable $listener The listener to remove
*/
public function removeListener($eventName, $listener);
public function removeSubscriber(EventSubscriberInterface $subscriber);
/**
* Gets the listeners of a specific event or all listeners sorted by descending priority.
*
* @param string|null $eventName The name of the event
*
* @return array The event listeners for the specified event, or all event listeners by event name
*/
public function getListeners($eventName = null);
/**
* Gets the listener priority for a specific event.
*
* Returns null if the event or the listener does not exist.
*
* @param string $eventName The name of the event
* @param callable $listener The listener
*
* @return int|null The event listener priority
*/
public function getListenerPriority($eventName, $listener);
/**
* Checks whether an event has any registered listeners.
*
* @param string|null $eventName The name of the event
*
* @return bool true if the specified event has any listeners, false otherwise
*/
public function hasListeners($eventName = null);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* @deprecated since Symfony 4.3, use "Symfony\Contracts\EventDispatcher\Event" instead
*/
class Event
{
private $propagationStopped = false;
/**
* @return bool Whether propagation was already stopped for this event
*
* @deprecated since Symfony 4.3, use "Symfony\Contracts\EventDispatcher\Event" instead
*/
public function isPropagationStopped()
{
return $this->propagationStopped;
}
/**
* @deprecated since Symfony 4.3, use "Symfony\Contracts\EventDispatcher\Event" instead
*/
public function stopPropagation()
{
$this->propagationStopped = true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
use Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as ContractsEventDispatcherInterface;
/**
* A helper class to provide BC/FC with the legacy signature of EventDispatcherInterface::dispatch().
*
* This class should be deprecated in Symfony 5.1
*
* @author Nicolas Grekas <p@tchwork.com>
*/
final class LegacyEventDispatcherProxy implements EventDispatcherInterface
{
private $dispatcher;
public static function decorate(?ContractsEventDispatcherInterface $dispatcher): ?ContractsEventDispatcherInterface
{
if (null === $dispatcher) {
return null;
}
$r = new \ReflectionMethod($dispatcher, 'dispatch');
$param2 = $r->getParameters()[1] ?? null;
if (!$param2 || !$param2->hasType() || $param2->getType()->isBuiltin()) {
return $dispatcher;
}
@trigger_error(sprintf('The signature of the "%s::dispatch()" method should be updated to "dispatch($event, string $eventName = null)", not doing so is deprecated since Symfony 4.3.', $r->class), E_USER_DEPRECATED);
$self = new self();
$self->dispatcher = $dispatcher;
return $self;
}
/**
* {@inheritdoc}
*
* @param string|null $eventName
*
* @return object
*/
public function dispatch($event/*, string $eventName = null*/)
{
$eventName = 1 < \func_num_args() ? func_get_arg(1) : null;
if (\is_object($event)) {
$eventName = $eventName ?? \get_class($event);
} elseif (\is_string($event) && (null === $eventName || $eventName instanceof ContractsEvent || $eventName instanceof Event)) {
@trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', ContractsEventDispatcherInterface::class), E_USER_DEPRECATED);
$swap = $event;
$event = $eventName ?? new Event();
$eventName = $swap;
} else {
throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, %s given.', ContractsEventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event)));
}
$listeners = $this->getListeners($eventName);
$stoppable = $event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface;
foreach ($listeners as $listener) {
if ($stoppable && $event->isPropagationStopped()) {
break;
}
$listener($event, $eventName, $this);
}
return $event;
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
return $this->dispatcher->addListener($eventName, $listener, $priority);
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
return $this->dispatcher->addSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
{
return $this->dispatcher->removeListener($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
return $this->dispatcher->removeSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null): array
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener): ?int
{
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null): bool
{
return $this->dispatcher->hasListeners($eventName);
}
/**
* Proxies all method calls to the original event dispatcher.
*/
public function __call($method, $arguments)
{
return $this->dispatcher->{$method}(...$arguments);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
use Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Component\EventDispatcher\Debug\WrappedListener;
use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
/**
* The EventDispatcherInterface is the central point of Symfony's event listener system.
*
* Listeners are registered on the manager and events are dispatched through the
* manager.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Jordan Alliot <jordan.alliot@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class EventDispatcher implements EventDispatcherInterface
{
private $listeners = [];
private $sorted = [];
private $optimized;
public function __construct()
{
if (__CLASS__ === \get_class($this)) {
$this->optimized = [];
}
}
/**
* {@inheritdoc}
*
* @param string|null $eventName
*/
public function dispatch($event/*, string $eventName = null*/)
{
$eventName = 1 < \func_num_args() ? func_get_arg(1) : null;
if (\is_object($event)) {
$eventName = $eventName ?? \get_class($event);
} elseif (\is_string($event) && (null === $eventName || $eventName instanceof ContractsEvent || $eventName instanceof Event)) {
@trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as the first argument is deprecated since Symfony 4.3, pass it as the second argument and provide the event object as the first argument instead.', EventDispatcherInterface::class), E_USER_DEPRECATED);
$swap = $event;
$event = $eventName ?? new Event();
$eventName = $swap;
} else {
throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an object, %s given.', EventDispatcherInterface::class, \is_object($event) ? \get_class($event) : \gettype($event)));
}
if (null !== $this->optimized && null !== $eventName) {
$listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
} else {
$listeners = $this->getListeners($eventName);
}
if ($listeners) {
$this->callListeners($listeners, $eventName, $event);
}
return $event;
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
if (null !== $eventName) {
if (empty($this->listeners[$eventName])) {
return [];
}
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
return $this->sorted[$eventName];
}
foreach ($this->listeners as $eventName => $eventListeners) {
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}
}
return array_filter($this->sorted);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
if (empty($this->listeners[$eventName])) {
return null;
}
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke';
}
foreach ($this->listeners[$eventName] as $priority => &$listeners) {
foreach ($listeners as &$v) {
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
$v[0] = $v[0]();
$v[1] = $v[1] ?? '__invoke';
}
if ($v === $listener) {
return $priority;
}
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
if (null !== $eventName) {
return !empty($this->listeners[$eventName]);
}
foreach ($this->listeners as $eventListeners) {
if ($eventListeners) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
$this->listeners[$eventName][$priority][] = $listener;
unset($this->sorted[$eventName], $this->optimized[$eventName]);
}
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
{
if (empty($this->listeners[$eventName])) {
return;
}
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke';
}
foreach ($this->listeners[$eventName] as $priority => &$listeners) {
foreach ($listeners as $k => &$v) {
if ($v !== $listener && \is_array($v) && isset($v[0]) && $v[0] instanceof \Closure && 2 >= \count($v)) {
$v[0] = $v[0]();
$v[1] = $v[1] ?? '__invoke';
}
if ($v === $listener) {
unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
}
}
if (!$listeners) {
unset($this->listeners[$eventName][$priority]);
}
}
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (\is_string($params)) {
$this->addListener($eventName, [$subscriber, $params]);
} elseif (\is_string($params[0])) {
$this->addListener($eventName, [$subscriber, $params[0]], isset($params[1]) ? $params[1] : 0);
} else {
foreach ($params as $listener) {
$this->addListener($eventName, [$subscriber, $listener[0]], isset($listener[1]) ? $listener[1] : 0);
}
}
}
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
if (\is_array($params) && \is_array($params[0])) {
foreach ($params as $listener) {
$this->removeListener($eventName, [$subscriber, $listener[0]]);
}
} else {
$this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]);
}
}
}
/**
* Triggers the listeners of an event.
*
* This method can be overridden to add functionality that is executed
* for each listener.
*
* @param callable[] $listeners The event listeners
* @param string $eventName The name of the event to dispatch
* @param object $event The event object to pass to the event handlers/listeners
*/
protected function callListeners(iterable $listeners, string $eventName, $event)
{
if ($event instanceof Event) {
$this->doDispatch($listeners, $eventName, $event);
return;
}
$stoppable = $event instanceof ContractsEvent || $event instanceof StoppableEventInterface;
foreach ($listeners as $listener) {
if ($stoppable && $event->isPropagationStopped()) {
break;
}
// @deprecated: the ternary operator is part of a BC layer and should be removed in 5.0
$listener($listener instanceof WrappedListener ? new LegacyEventProxy($event) : $event, $eventName, $this);
}
}
/**
* @deprecated since Symfony 4.3, use callListeners() instead
*/
protected function doDispatch($listeners, $eventName, Event $event)
{
foreach ($listeners as $listener) {
if ($event->isPropagationStopped()) {
break;
}
$listener($event, $eventName, $this);
}
}
/**
* Sorts the internal list of listeners for the given event by priority.
*/
private function sortListeners(string $eventName)
{
krsort($this->listeners[$eventName]);
$this->sorted[$eventName] = [];
foreach ($this->listeners[$eventName] as &$listeners) {
foreach ($listeners as $k => &$listener) {
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke';
}
$this->sorted[$eventName][] = $listener;
}
}
}
/**
* Optimizes the internal list of listeners for the given event by priority.
*/
private function optimizeListeners(string $eventName): array
{
krsort($this->listeners[$eventName]);
$this->optimized[$eventName] = [];
foreach ($this->listeners[$eventName] as &$listeners) {
foreach ($listeners as &$listener) {
$closure = &$this->optimized[$eventName][];
if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
$closure = static function (...$args) use (&$listener, &$closure) {
if ($listener[0] instanceof \Closure) {
$listener[0] = $listener[0]();
$listener[1] = $listener[1] ?? '__invoke';
}
($closure = \Closure::fromCallable($listener))(...$args);
};
} else {
$closure = $listener instanceof \Closure || $listener instanceof WrappedListener ? $listener : \Closure::fromCallable($listener);
}
}
}
return $this->optimized[$eventName];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* An EventSubscriber knows itself what events it is interested in.
* If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
* {@link getSubscribedEvents} and registers the subscriber as a listener for all
* returned events.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
*/
interface EventSubscriberInterface
{
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * ['eventName' => 'methodName']
* * ['eventName' => ['methodName', $priority]]
* * ['eventName' => [['methodName1', $priority], ['methodName2']]]
*
* @return array The event names to listen to
*/
public static function getSubscribedEvents();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* A read-only proxy for an event dispatcher.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class ImmutableEventDispatcher implements EventDispatcherInterface
{
private $dispatcher;
public function __construct(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
}
/**
* {@inheritdoc}
*
* @param string|null $eventName
*/
public function dispatch($event/*, string $eventName = null*/)
{
$eventName = 1 < \func_num_args() ? func_get_arg(1) : null;
if (is_scalar($event)) {
// deprecated
$swap = $event;
$event = $eventName ?? new Event();
$eventName = $swap;
}
return $this->dispatcher->dispatch($event, $eventName);
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
return $this->dispatcher->hasListeners($eventName);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
use Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
/**
* @internal to be removed in 5.0.
*/
final class LegacyEventProxy extends Event
{
private $event;
/**
* @param object $event
*/
public function __construct($event)
{
$this->event = $event;
}
/**
* @return object $event
*/
public function getEvent()
{
return $this->event;
}
public function isPropagationStopped(): bool
{
if (!$this->event instanceof ContractsEvent && !$this->event instanceof StoppableEventInterface) {
return false;
}
return $this->event->isPropagationStopped();
}
public function stopPropagation()
{
if (!$this->event instanceof ContractsEvent) {
return;
}
$this->event->stopPropagation();
}
public function __call($name, $args)
{
return $this->event->{$name}(...$args);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher;
/**
* Event encapsulation class.
*
* Encapsulates events thus decoupling the observer from the subject they encapsulate.
*
* @author Drak <drak@zikula.org>
*/
class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
{
protected $subject;
protected $arguments;
/**
* Encapsulate an event with $subject and $args.
*
* @param mixed $subject The subject of the event, usually an object or a callable
* @param array $arguments Arguments to store in the event
*/
public function __construct($subject = null, array $arguments = [])
{
$this->subject = $subject;
$this->arguments = $arguments;
}
/**
* Getter for subject property.
*
* @return mixed The observer subject
*/
public function getSubject()
{
return $this->subject;
}
/**
* Get argument by key.
*
* @param string $key Key
*
* @return mixed Contents of array key
*
* @throws \InvalidArgumentException if key is not found
*/
public function getArgument($key)
{
if ($this->hasArgument($key)) {
return $this->arguments[$key];
}
throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key));
}
/**
* Add argument to event.
*
* @param string $key Argument name
* @param mixed $value Value
*
* @return $this
*/
public function setArgument($key, $value)
{
$this->arguments[$key] = $value;
return $this;
}
/**
* Getter for all arguments.
*
* @return array
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Set args property.
*
* @param array $args Arguments
*
* @return $this
*/
public function setArguments(array $args = [])
{
$this->arguments = $args;
return $this;
}
/**
* Has argument.
*
* @param string $key Key of arguments array
*
* @return bool
*/
public function hasArgument($key)
{
return \array_key_exists($key, $this->arguments);
}
/**
* ArrayAccess for argument getter.
*
* @param string $key Array key
*
* @return mixed
*
* @throws \InvalidArgumentException if key does not exist in $this->args
*/
public function offsetGet($key)
{
return $this->getArgument($key);
}
/**
* ArrayAccess for argument setter.
*
* @param string $key Array key to set
* @param mixed $value Value
*/
public function offsetSet($key, $value)
{
$this->setArgument($key, $value);
}
/**
* ArrayAccess for unset argument.
*
* @param string $key Array key
*/
public function offsetUnset($key)
{
if ($this->hasArgument($key)) {
unset($this->arguments[$key]);
}
}
/**
* ArrayAccess has argument.
*
* @param string $key Array key
*
* @return bool
*/
public function offsetExists($key)
{
return $this->hasArgument($key);
}
/**
* IteratorAggregate for iterating over the object like an array.
*
* @return \ArrayIterator
*/
public function getIterator()
{
return new \ArrayIterator($this->arguments);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\DependencyInjection;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\Event as LegacyEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Compiler pass to register tagged services for an event dispatcher.
*/
class RegisterListenersPass implements CompilerPassInterface
{
protected $dispatcherService;
protected $listenerTag;
protected $subscriberTag;
protected $eventAliasesParameter;
private $hotPathEvents = [];
private $hotPathTagName;
public function __construct(string $dispatcherService = 'event_dispatcher', string $listenerTag = 'kernel.event_listener', string $subscriberTag = 'kernel.event_subscriber', string $eventAliasesParameter = 'event_dispatcher.event_aliases')
{
$this->dispatcherService = $dispatcherService;
$this->listenerTag = $listenerTag;
$this->subscriberTag = $subscriberTag;
$this->eventAliasesParameter = $eventAliasesParameter;
}
public function setHotPathEvents(array $hotPathEvents, $tagName = 'container.hot_path')
{
$this->hotPathEvents = array_flip($hotPathEvents);
$this->hotPathTagName = $tagName;
return $this;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) {
return;
}
if ($container->hasParameter($this->eventAliasesParameter)) {
$aliases = $container->getParameter($this->eventAliasesParameter);
$container->getParameterBag()->remove($this->eventAliasesParameter);
} else {
$aliases = [];
}
$definition = $container->findDefinition($this->dispatcherService);
foreach ($container->findTaggedServiceIds($this->listenerTag, true) as $id => $events) {
foreach ($events as $event) {
$priority = isset($event['priority']) ? $event['priority'] : 0;
if (!isset($event['event'])) {
if ($container->getDefinition($id)->hasTag($this->subscriberTag)) {
continue;
}
$event['method'] = $event['method'] ?? '__invoke';
$event['event'] = $this->getEventFromTypeDeclaration($container, $id, $event['method']);
}
$event['event'] = $aliases[$event['event']] ?? $event['event'];
if (!isset($event['method'])) {
$event['method'] = 'on'.preg_replace_callback([
'/(?<=\b)[a-z]/i',
'/[^a-z0-9]/i',
], function ($matches) { return strtoupper($matches[0]); }, $event['event']);
$event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']);
if (null !== ($class = $container->getDefinition($id)->getClass()) && ($r = $container->getReflectionClass($class, false)) && !$r->hasMethod($event['method']) && $r->hasMethod('__invoke')) {
$event['method'] = '__invoke';
}
}
$definition->addMethodCall('addListener', [$event['event'], [new ServiceClosureArgument(new Reference($id)), $event['method']], $priority]);
if (isset($this->hotPathEvents[$event['event']])) {
$container->getDefinition($id)->addTag($this->hotPathTagName);
}
}
}
$extractingDispatcher = new ExtractingEventDispatcher();
foreach ($container->findTaggedServiceIds($this->subscriberTag, true) as $id => $attributes) {
$def = $container->getDefinition($id);
// We must assume that the class value has been correctly filled, even if the service is created by a factory
$class = $def->getClass();
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(EventSubscriberInterface::class)) {
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class));
}
$class = $r->name;
ExtractingEventDispatcher::$aliases = $aliases;
ExtractingEventDispatcher::$subscriber = $class;
$extractingDispatcher->addSubscriber($extractingDispatcher);
foreach ($extractingDispatcher->listeners as $args) {
$args[1] = [new ServiceClosureArgument(new Reference($id)), $args[1]];
$definition->addMethodCall('addListener', $args);
if (isset($this->hotPathEvents[$args[0]])) {
$container->getDefinition($id)->addTag($this->hotPathTagName);
}
}
$extractingDispatcher->listeners = [];
ExtractingEventDispatcher::$aliases = [];
}
}
private function getEventFromTypeDeclaration(ContainerBuilder $container, string $id, string $method): string
{
if (
null === ($class = $container->getDefinition($id)->getClass())
|| !($r = $container->getReflectionClass($class, false))
|| !$r->hasMethod($method)
|| 1 > ($m = $r->getMethod($method))->getNumberOfParameters()
|| !($type = $m->getParameters()[0]->getType())
|| $type->isBuiltin()
|| Event::class === ($name = $type->getName())
|| LegacyEvent::class === $name
) {
throw new InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag));
}
return $name;
}
}
/**
* @internal
*/
class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
{
public $listeners = [];
public static $aliases = [];
public static $subscriber;
public function addListener($eventName, $listener, $priority = 0)
{
$this->listeners[] = [$eventName, $listener[1], $priority];
}
public static function getSubscribedEvents(): array
{
$events = [];
foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) {
$events[self::$aliases[$eventName] ?? $eventName] = $params;
}
return $events;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* This pass allows bundles to extend the list of event aliases.
*
* @author Alexander M. Turek <me@derrabus.de>
*/
class AddEventAliasesPass implements CompilerPassInterface
{
private $eventAliases;
private $eventAliasesParameter;
public function __construct(array $eventAliases, string $eventAliasesParameter = 'event_dispatcher.event_aliases')
{
$this->eventAliases = $eventAliases;
$this->eventAliasesParameter = $eventAliasesParameter;
}
public function process(ContainerBuilder $container): void
{
$eventAliases = $container->hasParameter($this->eventAliasesParameter) ? $container->getParameter($this->eventAliasesParameter) : [];
$container->setParameter(
$this->eventAliasesParameter,
array_merge($eventAliases, $this->eventAliases)
);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Debug;
use Psr\EventDispatcher\StoppableEventInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
use Symfony\Component\EventDispatcher\LegacyEventProxy;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
/**
* Collects some data about event listeners.
*
* This event dispatcher delegates the dispatching to another one.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TraceableEventDispatcher implements TraceableEventDispatcherInterface
{
protected $logger;
protected $stopwatch;
private $callStack;
private $dispatcher;
private $wrappedListeners;
private $orphanedEvents;
private $requestStack;
private $currentRequestHash = '';
public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null, RequestStack $requestStack = null)
{
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
$this->stopwatch = $stopwatch;
$this->logger = $logger;
$this->wrappedListeners = [];
$this->orphanedEvents = [];
$this->requestStack = $requestStack;
}
/**
* {@inheritdoc}
*/
public function addListener($eventName, $listener, $priority = 0)
{
$this->dispatcher->addListener($eventName, $listener, $priority);
}
/**
* {@inheritdoc}
*/
public function addSubscriber(EventSubscriberInterface $subscriber)
{
$this->dispatcher->addSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
public function removeListener($eventName, $listener)
{
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
$listener = $wrappedListener;
unset($this->wrappedListeners[$eventName][$index]);
break;
}
}
}
return $this->dispatcher->removeListener($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function removeSubscriber(EventSubscriberInterface $subscriber)
{
return $this->dispatcher->removeSubscriber($subscriber);
}
/**
* {@inheritdoc}
*/
public function getListeners($eventName = null)
{
return $this->dispatcher->getListeners($eventName);
}
/**
* {@inheritdoc}
*/
public function getListenerPriority($eventName, $listener)
{
// we might have wrapped listeners for the event (if called while dispatching)
// in that case get the priority by wrapper
if (isset($this->wrappedListeners[$eventName])) {
foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
if ($wrappedListener->getWrappedListener() === $listener) {
return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
}
}
}
return $this->dispatcher->getListenerPriority($eventName, $listener);
}
/**
* {@inheritdoc}
*/
public function hasListeners($eventName = null)
{
return $this->dispatcher->hasListeners($eventName);
}
/**
* {@inheritdoc}
*
* @param string|null $eventName
*/
public function dispatch($event/*, string $eventName = null*/)
{
if (null === $this->callStack) {
$this->callStack = new \SplObjectStorage();
}
$currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';
$eventName = 1 < \func_num_args() ? func_get_arg(1) : null;
if (\is_object($event)) {
$eventName = $eventName ?? \get_class($event);
} else {
@trigger_error(sprintf('Calling the "%s::dispatch()" method with the event name as first argument is deprecated since Symfony 4.3, pass it second and provide the event object first instead.', EventDispatcherInterface::class), E_USER_DEPRECATED);
$swap = $event;
$event = $eventName ?? new Event();
$eventName = $swap;
if (!$event instanceof Event) {
throw new \TypeError(sprintf('Argument 1 passed to "%s::dispatch()" must be an instance of %s, %s given.', EventDispatcherInterface::class, Event::class, \is_object($event) ? \get_class($event) : \gettype($event)));
}
}
if (null !== $this->logger && ($event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface) && $event->isPropagationStopped()) {
$this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName));
}
$this->preProcess($eventName);
try {
$this->beforeDispatch($eventName, $event);
try {
$e = $this->stopwatch->start($eventName, 'section');
try {
$this->dispatcher->dispatch($event, $eventName);
} finally {
if ($e->isStarted()) {
$e->stop();
}
}
} finally {
$this->afterDispatch($eventName, $event);
}
} finally {
$this->currentRequestHash = $currentRequestHash;
$this->postProcess($eventName);
}
return $event;
}
/**
* {@inheritdoc}
*
* @param Request|null $request The request to get listeners for
*/
public function getCalledListeners(/* Request $request = null */)
{
if (null === $this->callStack) {
return [];
}
$hash = 1 <= \func_num_args() && null !== ($request = func_get_arg(0)) ? spl_object_hash($request) : null;
$called = [];
foreach ($this->callStack as $listener) {
list($eventName, $requestHash) = $this->callStack->getInfo();
if (null === $hash || $hash === $requestHash) {
$called[] = $listener->getInfo($eventName);
}
}
return $called;
}
/**
* {@inheritdoc}
*
* @param Request|null $request The request to get listeners for
*/
public function getNotCalledListeners(/* Request $request = null */)
{
try {
$allListeners = $this->getListeners();
} catch (\Exception $e) {
if (null !== $this->logger) {
$this->logger->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);
}
// unable to retrieve the uncalled listeners
return [];
}
$hash = 1 <= \func_num_args() && null !== ($request = func_get_arg(0)) ? spl_object_hash($request) : null;
$calledListeners = [];
if (null !== $this->callStack) {
foreach ($this->callStack as $calledListener) {
list(, $requestHash) = $this->callStack->getInfo();
if (null === $hash || $hash === $requestHash) {
$calledListeners[] = $calledListener->getWrappedListener();
}
}
}
$notCalled = [];
foreach ($allListeners as $eventName => $listeners) {
foreach ($listeners as $listener) {
if (!\in_array($listener, $calledListeners, true)) {
if (!$listener instanceof WrappedListener) {
$listener = new WrappedListener($listener, null, $this->stopwatch, $this);
}
$notCalled[] = $listener->getInfo($eventName);
}
}
}
uasort($notCalled, [$this, 'sortNotCalledListeners']);
return $notCalled;
}
/**
* @param Request|null $request The request to get orphaned events for
*/
public function getOrphanedEvents(/* Request $request = null */): array
{
if (1 <= \func_num_args() && null !== $request = func_get_arg(0)) {
return $this->orphanedEvents[spl_object_hash($request)] ?? [];
}
if (!$this->orphanedEvents) {
return [];
}
return array_merge(...array_values($this->orphanedEvents));
}
public function reset()
{
$this->callStack = null;
$this->orphanedEvents = [];
$this->currentRequestHash = '';
}
/**
* Proxies all method calls to the original event dispatcher.
*
* @param string $method The method name
* @param array $arguments The method arguments
*
* @return mixed
*/
public function __call($method, $arguments)
{
return $this->dispatcher->{$method}(...$arguments);
}
/**
* Called before dispatching the event.
*
* @param object $event
*/
protected function beforeDispatch(string $eventName, $event)
{
$this->preDispatch($eventName, $event instanceof Event ? $event : new LegacyEventProxy($event));
}
/**
* Called after dispatching the event.
*
* @param object $event
*/
protected function afterDispatch(string $eventName, $event)
{
$this->postDispatch($eventName, $event instanceof Event ? $event : new LegacyEventProxy($event));
}
/**
* @deprecated since Symfony 4.3, will be removed in 5.0, use beforeDispatch instead
*/
protected function preDispatch($eventName, Event $event)
{
}
/**
* @deprecated since Symfony 4.3, will be removed in 5.0, use afterDispatch instead
*/
protected function postDispatch($eventName, Event $event)
{
}
private function preProcess(string $eventName)
{
if (!$this->dispatcher->hasListeners($eventName)) {
$this->orphanedEvents[$this->currentRequestHash][] = $eventName;
return;
}
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
$priority = $this->getListenerPriority($eventName, $listener);
$wrappedListener = new WrappedListener($listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener, null, $this->stopwatch, $this);
$this->wrappedListeners[$eventName][] = $wrappedListener;
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $wrappedListener, $priority);
$this->callStack->attach($wrappedListener, [$eventName, $this->currentRequestHash]);
}
}
private function postProcess(string $eventName)
{
unset($this->wrappedListeners[$eventName]);
$skipped = false;
foreach ($this->dispatcher->getListeners($eventName) as $listener) {
if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
continue;
}
// Unwrap listener
$priority = $this->getListenerPriority($eventName, $listener);
$this->dispatcher->removeListener($eventName, $listener);
$this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);
if (null !== $this->logger) {
$context = ['event' => $eventName, 'listener' => $listener->getPretty()];
}
if ($listener->wasCalled()) {
if (null !== $this->logger) {
$this->logger->debug('Notified event "{event}" to listener "{listener}".', $context);
}
} else {
$this->callStack->detach($listener);
}
if (null !== $this->logger && $skipped) {
$this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
}
if ($listener->stoppedPropagation()) {
if (null !== $this->logger) {
$this->logger->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);
}
$skipped = true;
}
}
}
private function sortNotCalledListeners(array $a, array $b)
{
if (0 !== $cmp = strcmp($a['event'], $b['event'])) {
return $cmp;
}
if (\is_int($a['priority']) && !\is_int($b['priority'])) {
return 1;
}
if (!\is_int($a['priority']) && \is_int($b['priority'])) {
return -1;
}
if ($a['priority'] === $b['priority']) {
return 0;
}
if ($a['priority'] > $b['priority']) {
return -1;
}
return 1;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Debug;
use Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\LegacyEventProxy;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\VarDumper\Caster\ClassStub;
use Symfony\Contracts\EventDispatcher\Event as ContractsEvent;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since Symfony 4.3: the "Event" type-hint on __invoke() will be replaced by "object" in 5.0
*/
class WrappedListener
{
private $listener;
private $optimizedListener;
private $name;
private $called;
private $stoppedPropagation;
private $stopwatch;
private $dispatcher;
private $pretty;
private $stub;
private $priority;
private static $hasClassStub;
public function __construct($listener, ?string $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null)
{
$this->listener = $listener;
$this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? \Closure::fromCallable($listener) : null);
$this->stopwatch = $stopwatch;
$this->dispatcher = $dispatcher;
$this->called = false;
$this->stoppedPropagation = false;
if (\is_array($listener)) {
$this->name = \is_object($listener[0]) ? \get_class($listener[0]) : $listener[0];
$this->pretty = $this->name.'::'.$listener[1];
} elseif ($listener instanceof \Closure) {
$r = new \ReflectionFunction($listener);
if (false !== strpos($r->name, '{closure}')) {
$this->pretty = $this->name = 'closure';
} elseif ($class = $r->getClosureScopeClass()) {
$this->name = $class->name;
$this->pretty = $this->name.'::'.$r->name;
} else {
$this->pretty = $this->name = $r->name;
}
} elseif (\is_string($listener)) {
$this->pretty = $this->name = $listener;
} else {
$this->name = \get_class($listener);
$this->pretty = $this->name.'::__invoke';
}
if (null !== $name) {
$this->name = $name;
}
if (null === self::$hasClassStub) {
self::$hasClassStub = class_exists(ClassStub::class);
}
}
public function getWrappedListener()
{
return $this->listener;
}
public function wasCalled()
{
return $this->called;
}
public function stoppedPropagation()
{
return $this->stoppedPropagation;
}
public function getPretty()
{
return $this->pretty;
}
public function getInfo($eventName)
{
if (null === $this->stub) {
$this->stub = self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->listener) : $this->pretty.'()';
}
return [
'event' => $eventName,
'priority' => null !== $this->priority ? $this->priority : (null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null),
'pretty' => $this->pretty,
'stub' => $this->stub,
];
}
public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher)
{
if ($event instanceof LegacyEventProxy) {
$event = $event->getEvent();
}
$dispatcher = $this->dispatcher ?: $dispatcher;
$this->called = true;
$this->priority = $dispatcher->getListenerPriority($eventName, $this->listener);
$e = $this->stopwatch->start($this->name, 'event_listener');
($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher);
if ($e->isStarted()) {
$e->stop();
}
if (($event instanceof Event || $event instanceof ContractsEvent || $event instanceof StoppableEventInterface) && $event->isPropagationStopped()) {
$this->stoppedPropagation = true;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\EventDispatcher\Debug;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Contracts\Service\ResetInterface;
/**
* @deprecated since Symfony 4.1
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface TraceableEventDispatcherInterface extends EventDispatcherInterface, ResetInterface
{
/**
* Gets the called listeners.
*
* @param Request|null $request The request to get listeners for
*
* @return array An array of called listeners
*/
public function getCalledListeners(/* Request $request = null */);
/**
* Gets the not called listeners.
*
* @param Request|null $request The request to get listeners for
*
* @return array An array of not called listeners
*/
public function getNotCalledListeners(/* Request $request = null */);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Symfony\Component\Filesystem\Exception\InvalidArgumentException;
use Symfony\Component\Filesystem\Exception\IOException;
/**
* Provides basic utility to manipulate the file system.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Filesystem
{
private static $lastError;
/**
* Copies a file.
*
* If the target file is older than the origin file, it's always overwritten.
* If the target file is newer, it is overwritten only when the
* $overwriteNewerFiles option is set to true.
*
* @param string $originFile The original filename
* @param string $targetFile The target filename
* @param bool $overwriteNewerFiles If true, target files newer than origin files are overwritten
*
* @throws FileNotFoundException When originFile doesn't exist
* @throws IOException When copy fails
*/
public function copy($originFile, $targetFile, $overwriteNewerFiles = false)
{
$originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://');
if ($originIsLocal && !is_file($originFile)) {
throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
}
$this->mkdir(\dirname($targetFile));
$doCopy = true;
if (!$overwriteNewerFiles && null === parse_url($originFile, PHP_URL_HOST) && is_file($targetFile)) {
$doCopy = filemtime($originFile) > filemtime($targetFile);
}
if ($doCopy) {
// https://bugs.php.net/64634
if (false === $source = @fopen($originFile, 'r')) {
throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile);
}
// Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(['ftp' => ['overwrite' => true]]))) {
throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile);
}
$bytesCopied = stream_copy_to_stream($source, $target);
fclose($source);
fclose($target);
unset($source, $target);
if (!is_file($targetFile)) {
throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
}
if ($originIsLocal) {
// Like `cp`, preserve executable permission bits
@chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));
if ($bytesCopied !== $bytesOrigin = filesize($originFile)) {
throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
}
}
}
}
/**
* Creates a directory recursively.
*
* @param string|iterable $dirs The directory path
* @param int $mode The directory mode
*
* @throws IOException On any directory creation failure
*/
public function mkdir($dirs, $mode = 0777)
{
foreach ($this->toIterable($dirs) as $dir) {
if (is_dir($dir)) {
continue;
}
if (!self::box('mkdir', $dir, $mode, true)) {
if (!is_dir($dir)) {
// The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
if (self::$lastError) {
throw new IOException(sprintf('Failed to create "%s": %s.', $dir, self::$lastError), 0, null, $dir);
}
throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $dir);
}
}
}
}
/**
* Checks the existence of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to check
*
* @return bool true if the file exists, false otherwise
*/
public function exists($files)
{
$maxPathLength = PHP_MAXPATHLEN - 2;
foreach ($this->toIterable($files) as $file) {
if (\strlen($file) > $maxPathLength) {
throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file);
}
if (!file_exists($file)) {
return false;
}
}
return true;
}
/**
* Sets access and modification time of file.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to create
* @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used
* @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used
*
* @throws IOException When touch fails
*/
public function touch($files, $time = null, $atime = null)
{
foreach ($this->toIterable($files) as $file) {
$touch = $time ? @touch($file, $time, $atime) : @touch($file);
if (true !== $touch) {
throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file);
}
}
}
/**
* Removes files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove
*
* @throws IOException When removal fails
*/
public function remove($files)
{
if ($files instanceof \Traversable) {
$files = iterator_to_array($files, false);
} elseif (!\is_array($files)) {
$files = [$files];
}
$files = array_reverse($files);
foreach ($files as $file) {
if (is_link($file)) {
// See https://bugs.php.net/52176
if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, self::$lastError));
}
} elseif (is_dir($file)) {
$this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));
if (!self::box('rmdir', $file) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, self::$lastError));
}
} elseif (!self::box('unlink', $file) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, self::$lastError));
}
}
}
/**
* Change mode for an array of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change mode
* @param int $mode The new mode (octal)
* @param int $umask The mode mask (octal)
* @param bool $recursive Whether change the mod recursively or not
*
* @throws IOException When the change fails
*/
public function chmod($files, $mode, $umask = 0000, $recursive = false)
{
foreach ($this->toIterable($files) as $file) {
if (true !== @chmod($file, $mode & ~$umask)) {
throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file);
}
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chmod(new \FilesystemIterator($file), $mode, $umask, true);
}
}
}
/**
* Change the owner of an array of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner
* @param string|int $user A user name or number
* @param bool $recursive Whether change the owner recursively or not
*
* @throws IOException When the change fails
*/
public function chown($files, $user, $recursive = false)
{
foreach ($this->toIterable($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chown(new \FilesystemIterator($file), $user, true);
}
if (is_link($file) && \function_exists('lchown')) {
if (true !== @lchown($file, $user)) {
throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
}
} else {
if (true !== @chown($file, $user)) {
throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
}
}
}
}
/**
* Change the group of an array of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group
* @param string|int $group A group name or number
* @param bool $recursive Whether change the group recursively or not
*
* @throws IOException When the change fails
*/
public function chgrp($files, $group, $recursive = false)
{
foreach ($this->toIterable($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chgrp(new \FilesystemIterator($file), $group, true);
}
if (is_link($file) && \function_exists('lchgrp')) {
if (true !== @lchgrp($file, $group)) {
throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
}
} else {
if (true !== @chgrp($file, $group)) {
throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
}
}
}
}
/**
* Renames a file or a directory.
*
* @param string $origin The origin filename or directory
* @param string $target The new filename or directory
* @param bool $overwrite Whether to overwrite the target if it already exists
*
* @throws IOException When target file or directory already exists
* @throws IOException When origin cannot be renamed
*/
public function rename($origin, $target, $overwrite = false)
{
// we check that target does not exist
if (!$overwrite && $this->isReadable($target)) {
throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
}
if (true !== @rename($origin, $target)) {
if (is_dir($origin)) {
// See https://bugs.php.net/54097 & https://php.net/rename#113943
$this->mirror($origin, $target, null, ['override' => $overwrite, 'delete' => $overwrite]);
$this->remove($origin);
return;
}
throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target);
}
}
/**
* Tells whether a file exists and is readable.
*
* @throws IOException When windows path is longer than 258 characters
*/
private function isReadable(string $filename): bool
{
$maxPathLength = PHP_MAXPATHLEN - 2;
if (\strlen($filename) > $maxPathLength) {
throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename);
}
return is_readable($filename);
}
/**
* Creates a symbolic link or copy a directory.
*
* @param string $originDir The origin directory path
* @param string $targetDir The symbolic link name
* @param bool $copyOnWindows Whether to copy files if on Windows
*
* @throws IOException When symlink fails
*/
public function symlink($originDir, $targetDir, $copyOnWindows = false)
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$originDir = strtr($originDir, '/', '\\');
$targetDir = strtr($targetDir, '/', '\\');
if ($copyOnWindows) {
$this->mirror($originDir, $targetDir);
return;
}
}
$this->mkdir(\dirname($targetDir));
if (is_link($targetDir)) {
if (readlink($targetDir) === $originDir) {
return;
}
$this->remove($targetDir);
}
if (!self::box('symlink', $originDir, $targetDir)) {
$this->linkException($originDir, $targetDir, 'symbolic');
}
}
/**
* Creates a hard link, or several hard links to a file.
*
* @param string $originFile The original file
* @param string|string[] $targetFiles The target file(s)
*
* @throws FileNotFoundException When original file is missing or not a file
* @throws IOException When link fails, including if link already exists
*/
public function hardlink($originFile, $targetFiles)
{
if (!$this->exists($originFile)) {
throw new FileNotFoundException(null, 0, null, $originFile);
}
if (!is_file($originFile)) {
throw new FileNotFoundException(sprintf('Origin file "%s" is not a file', $originFile));
}
foreach ($this->toIterable($targetFiles) as $targetFile) {
if (is_file($targetFile)) {
if (fileinode($originFile) === fileinode($targetFile)) {
continue;
}
$this->remove($targetFile);
}
if (!self::box('link', $originFile, $targetFile)) {
$this->linkException($originFile, $targetFile, 'hard');
}
}
}
/**
* @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
*/
private function linkException(string $origin, string $target, string $linkType)
{
if (self::$lastError) {
if ('\\' === \DIRECTORY_SEPARATOR && false !== strpos(self::$lastError, 'error code(1314)')) {
throw new IOException(sprintf('Unable to create %s link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target);
}
}
throw new IOException(sprintf('Failed to create %s link from "%s" to "%s".', $linkType, $origin, $target), 0, null, $target);
}
/**
* Resolves links in paths.
*
* With $canonicalize = false (default)
* - if $path does not exist or is not a link, returns null
* - if $path is a link, returns the next direct target of the link without considering the existence of the target
*
* With $canonicalize = true
* - if $path does not exist, returns null
* - if $path exists, returns its absolute fully resolved final version
*
* @param string $path A filesystem path
* @param bool $canonicalize Whether or not to return a canonicalized path
*
* @return string|null
*/
public function readlink($path, $canonicalize = false)
{
if (!$canonicalize && !is_link($path)) {
return null;
}
if ($canonicalize) {
if (!$this->exists($path)) {
return null;
}
if ('\\' === \DIRECTORY_SEPARATOR) {
$path = readlink($path);
}
return realpath($path);
}
if ('\\' === \DIRECTORY_SEPARATOR) {
return realpath($path);
}
return readlink($path);
}
/**
* Given an existing path, convert it to a path relative to a given starting path.
*
* @param string $endPath Absolute path of target
* @param string $startPath Absolute path where traversal begins
*
* @return string Path of target relative to starting path
*/
public function makePathRelative($endPath, $startPath)
{
if (!$this->isAbsolutePath($startPath)) {
throw new InvalidArgumentException(sprintf('The start path "%s" is not absolute.', $startPath));
}
if (!$this->isAbsolutePath($endPath)) {
throw new InvalidArgumentException(sprintf('The end path "%s" is not absolute.', $endPath));
}
// Normalize separators on Windows
if ('\\' === \DIRECTORY_SEPARATOR) {
$endPath = str_replace('\\', '/', $endPath);
$startPath = str_replace('\\', '/', $startPath);
}
$stripDriveLetter = function ($path) {
if (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) {
return substr($path, 2);
}
return $path;
};
$endPath = $stripDriveLetter($endPath);
$startPath = $stripDriveLetter($startPath);
// Split the paths into arrays
$startPathArr = explode('/', trim($startPath, '/'));
$endPathArr = explode('/', trim($endPath, '/'));
$normalizePathArray = function ($pathSegments) {
$result = [];
foreach ($pathSegments as $segment) {
if ('..' === $segment) {
array_pop($result);
} elseif ('.' !== $segment) {
$result[] = $segment;
}
}
return $result;
};
$startPathArr = $normalizePathArray($startPathArr);
$endPathArr = $normalizePathArray($endPathArr);
// Find for which directory the common path stops
$index = 0;
while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
++$index;
}
// Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
if (1 === \count($startPathArr) && '' === $startPathArr[0]) {
$depth = 0;
} else {
$depth = \count($startPathArr) - $index;
}
// Repeated "../" for each level need to reach the common path
$traverser = str_repeat('../', $depth);
$endPathRemainder = implode('/', \array_slice($endPathArr, $index));
// Construct $endPath from traversing to the common path, then to the remaining $endPath
$relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');
return '' === $relativePath ? './' : $relativePath;
}
/**
* Mirrors a directory to another.
*
* Copies files and directories from the origin directory into the target directory. By default:
*
* - existing files in the target directory will be overwritten, except if they are newer (see the `override` option)
* - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option)
*
* @param string $originDir The origin directory
* @param string $targetDir The target directory
* @param \Traversable|null $iterator Iterator that filters which files and directories to copy, if null a recursive iterator is created
* @param array $options An array of boolean options
* Valid options are:
* - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false)
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
*
* @throws IOException When file type is unknown
*/
public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = [])
{
$targetDir = rtrim($targetDir, '/\\');
$originDir = rtrim($originDir, '/\\');
$originDirLen = \strlen($originDir);
if (!$this->exists($originDir)) {
throw new IOException(sprintf('The origin directory specified "%s" was not found.', $originDir), 0, null, $originDir);
}
// Iterate in destination folder to remove obsolete entries
if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) {
$deleteIterator = $iterator;
if (null === $deleteIterator) {
$flags = \FilesystemIterator::SKIP_DOTS;
$deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
}
$targetDirLen = \strlen($targetDir);
foreach ($deleteIterator as $file) {
$origin = $originDir.substr($file->getPathname(), $targetDirLen);
if (!$this->exists($origin)) {
$this->remove($file);
}
}
}
$copyOnWindows = $options['copy_on_windows'] ?? false;
if (null === $iterator) {
$flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS;
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST);
}
$this->mkdir($targetDir);
$filesCreatedWhileMirroring = [];
foreach ($iterator as $file) {
if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) {
continue;
}
$target = $targetDir.substr($file->getPathname(), $originDirLen);
$filesCreatedWhileMirroring[$target] = true;
if (!$copyOnWindows && is_link($file)) {
$this->symlink($file->getLinkTarget(), $target);
} elseif (is_dir($file)) {
$this->mkdir($target);
} elseif (is_file($file)) {
$this->copy($file, $target, isset($options['override']) ? $options['override'] : false);
} else {
throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
}
}
}
/**
* Returns whether the file path is an absolute path.
*
* @param string $file A file path
*
* @return bool
*/
public function isAbsolutePath($file)
{
if (null === $file) {
@trigger_error(sprintf('Calling "%s()" with a null in the $file argument is deprecated since Symfony 4.4.', __METHOD__), E_USER_DEPRECATED);
}
return strspn($file, '/\\', 0, 1)
|| (\strlen($file) > 3 && ctype_alpha($file[0])
&& ':' === $file[1]
&& strspn($file, '/\\', 2, 1)
)
|| null !== parse_url($file, PHP_URL_SCHEME)
;
}
/**
* Creates a temporary file with support for custom stream wrappers.
*
* @param string $dir The directory where the temporary filename will be created
* @param string $prefix The prefix of the generated temporary filename
* Note: Windows uses only the first three characters of prefix
*
* @return string The new temporary filename (with path), or throw an exception on failure
*/
public function tempnam($dir, $prefix)
{
list($scheme, $hierarchy) = $this->getSchemeAndHierarchy($dir);
// If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem
if (null === $scheme || 'file' === $scheme || 'gs' === $scheme) {
$tmpFile = @tempnam($hierarchy, $prefix);
// If tempnam failed or no scheme return the filename otherwise prepend the scheme
if (false !== $tmpFile) {
if (null !== $scheme && 'gs' !== $scheme) {
return $scheme.'://'.$tmpFile;
}
return $tmpFile;
}
throw new IOException('A temporary file could not be created.');
}
// Loop until we create a valid temp file or have reached 10 attempts
for ($i = 0; $i < 10; ++$i) {
// Create a unique filename
$tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true);
// Use fopen instead of file_exists as some streams do not support stat
// Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
$handle = @fopen($tmpFile, 'x+');
// If unsuccessful restart the loop
if (false === $handle) {
continue;
}
// Close the file if it was successfully opened
@fclose($handle);
return $tmpFile;
}
throw new IOException('A temporary file could not be created.');
}
/**
* Atomically dumps content into a file.
*
* @param string $filename The file to be written to
* @param string|resource $content The data to write into the file
*
* @throws IOException if the file cannot be written to
*/
public function dumpFile($filename, $content)
{
if (\is_array($content)) {
@trigger_error(sprintf('Calling "%s()" with an array in the $content argument is deprecated since Symfony 4.3.', __METHOD__), E_USER_DEPRECATED);
}
$dir = \dirname($filename);
if (!is_dir($dir)) {
$this->mkdir($dir);
}
if (!is_writable($dir)) {
throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
}
// Will create a temp file with 0600 access rights
// when the filesystem supports chmod.
$tmpFile = $this->tempnam($dir, basename($filename));
if (false === @file_put_contents($tmpFile, $content)) {
throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
}
@chmod($tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask());
$this->rename($tmpFile, $filename, true);
}
/**
* Appends content to an existing file.
*
* @param string $filename The file to which to append content
* @param string|resource $content The content to append
*
* @throws IOException If the file is not writable
*/
public function appendToFile($filename, $content)
{
if (\is_array($content)) {
@trigger_error(sprintf('Calling "%s()" with an array in the $content argument is deprecated since Symfony 4.3.', __METHOD__), E_USER_DEPRECATED);
}
$dir = \dirname($filename);
if (!is_dir($dir)) {
$this->mkdir($dir);
}
if (!is_writable($dir)) {
throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
}
if (false === @file_put_contents($filename, $content, FILE_APPEND)) {
throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
}
}
private function toIterable($files): iterable
{
return \is_array($files) || $files instanceof \Traversable ? $files : [$files];
}
/**
* Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]).
*/
private function getSchemeAndHierarchy(string $filename): array
{
$components = explode('://', $filename, 2);
return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]];
}
/**
* @return mixed
*/
private static function box(callable $func)
{
self::$lastError = null;
set_error_handler(__CLASS__.'::handleError');
try {
$result = $func(...\array_slice(\func_get_args(), 1));
restore_error_handler();
return $result;
} catch (\Throwable $e) {
}
restore_error_handler();
throw $e;
}
/**
* @internal
*/
public static function handleError($type, $msg)
{
self::$lastError = $msg;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* IOException interface for file and input/output stream related exceptions thrown by the component.
*
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
*/
interface IOExceptionInterface extends ExceptionInterface
{
/**
* Returns the associated path for the exception.
*
* @return string|null The path
*/
public function getPath();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception interface for all exceptions thrown by the component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception class thrown when a filesystem operation failure happens.
*
* @author Romain Neutron <imprec@gmail.com>
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class IOException extends \RuntimeException implements IOExceptionInterface
{
private $path;
public function __construct(string $message, int $code = 0, \Throwable $previous = null, string $path = null)
{
$this->path = $path;
parent::__construct($message, $code, $previous);
}
/**
* {@inheritdoc}
*/
public function getPath()
{
return $this->path;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception class thrown when a file couldn't be found.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
*/
class FileNotFoundException extends IOException
{
public function __construct(string $message = null, int $code = 0, \Throwable $previous = null, string $path = null)
{
if (null === $message) {
if (null === $path) {
$message = 'File could not be found.';
} else {
$message = sprintf('File "%s" could not be found.', $path);
}
}
parent::__construct($message, $code, $previous, $path);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\EventDispatcher;
use Psr\EventDispatcher\EventDispatcherInterface as PsrEventDispatcherInterface;
if (interface_exists(PsrEventDispatcherInterface::class)) {
/**
* Allows providing hooks on domain-specific lifecycles by dispatching events.
*/
interface EventDispatcherInterface extends PsrEventDispatcherInterface
{
/**
* Dispatches an event to all registered listeners.
*
* For BC with Symfony 4, the $eventName argument is not declared explicitly on the
* signature of the method. Implementations that are not bound by this BC constraint
* MUST declare it explicitly, as allowed by PHP.
*
* @param object $event The event to pass to the event handlers/listeners
* @param string|null $eventName The name of the event to dispatch. If not supplied,
* the class of $event should be used instead.
*
* @return object The passed $event MUST be returned
*/
public function dispatch($event/*, string $eventName = null*/);
}
} else {
/**
* Allows providing hooks on domain-specific lifecycles by dispatching events.
*/
interface EventDispatcherInterface
{
/**
* Dispatches an event to all registered listeners.
*
* For BC with Symfony 4, the $eventName argument is not declared explicitly on the
* signature of the method. Implementations that are not bound by this BC constraint
* MUST declare it explicitly, as allowed by PHP.
*
* @param object $event The event to pass to the event handlers/listeners
* @param string|null $eventName The name of the event to dispatch. If not supplied,
* the class of $event should be used instead.
*
* @return object The passed $event MUST be returned
*/
public function dispatch($event/*, string $eventName = null*/);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\EventDispatcher;
use Psr\EventDispatcher\StoppableEventInterface;
if (interface_exists(StoppableEventInterface::class)) {
/**
* Event is the base class for classes containing event data.
*
* This class contains no event data. It is used by events that do not pass
* state information to an event handler when an event is raised.
*
* You can call the method stopPropagation() to abort the execution of
* further listeners in your event listener.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class Event implements StoppableEventInterface
{
private $propagationStopped = false;
/**
* Returns whether further event listeners should be triggered.
*/
public function isPropagationStopped(): bool
{
return $this->propagationStopped;
}
/**
* Stops the propagation of the event to further event listeners.
*
* If multiple event listeners are connected to the same event, no
* further event listener will be triggered once any trigger calls
* stopPropagation().
*/
public function stopPropagation(): void
{
$this->propagationStopped = true;
}
}
} else {
/**
* Event is the base class for classes containing event data.
*
* This class contains no event data. It is used by events that do not pass
* state information to an event handler when an event is raised.
*
* You can call the method stopPropagation() to abort the execution of
* further listeners in your event listener.
*
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class Event
{
private $propagationStopped = false;
/**
* Returns whether further event listeners should be triggered.
*/
public function isPropagationStopped(): bool
{
return $this->propagationStopped;
}
/**
* Stops the propagation of the event to further event listeners.
*
* If multiple event listeners are connected to the same event, no
* further event listener will be triggered once any trigger calls
* stopPropagation().
*/
public function stopPropagation(): void
{
$this->propagationStopped = true;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Extends \SplFileInfo to support relative paths.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SplFileInfo extends \SplFileInfo
{
private $relativePath;
private $relativePathname;
/**
* @param string $file The file name
* @param string $relativePath The relative path
* @param string $relativePathname The relative path name
*/
public function __construct(string $file, string $relativePath, string $relativePathname)
{
parent::__construct($file);
$this->relativePath = $relativePath;
$this->relativePathname = $relativePathname;
}
/**
* Returns the relative path.
*
* This path does not contain the file name.
*
* @return string the relative path
*/
public function getRelativePath()
{
return $this->relativePath;
}
/**
* Returns the relative path name.
*
* This path contains the file name.
*
* @return string the relative path name
*/
public function getRelativePathname()
{
return $this->relativePathname;
}
public function getFilenameWithoutExtension(): string
{
$filename = $this->getFilename();
return pathinfo($filename, PATHINFO_FILENAME);
}
/**
* Returns the contents of the file.
*
* @return string the contents of the file
*
* @throws \RuntimeException
*/
public function getContents()
{
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$content = file_get_contents($this->getPathname());
restore_error_handler();
if (false === $content) {
throw new \RuntimeException($error);
}
return $content;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* NumberComparator compiles a simple comparison to an anonymous
* subroutine, which you can call with a value to be tested again.
*
* Now this would be very pointless, if NumberCompare didn't understand
* magnitudes.
*
* The target value may use magnitudes of kilobytes (k, ki),
* megabytes (m, mi), or gigabytes (g, gi). Those suffixed
* with an i use the appropriate 2**n version in accordance with the
* IEC standard: http://physics.nist.gov/cuu/Units/binary.html
*
* Based on the Perl Number::Compare module.
*
* @author Fabien Potencier <fabien@symfony.com> PHP port
* @author Richard Clamp <richardc@unixbeard.net> Perl version
* @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
* @copyright 2002 Richard Clamp <richardc@unixbeard.net>
*
* @see http://physics.nist.gov/cuu/Units/binary.html
*/
class NumberComparator extends Comparator
{
/**
* @param string|int $test A comparison string or an integer
*
* @throws \InvalidArgumentException If the test is not understood
*/
public function __construct(?string $test)
{
if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) {
throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test));
}
$target = $matches[2];
if (!is_numeric($target)) {
throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target));
}
if (isset($matches[3])) {
// magnitude
switch (strtolower($matches[3])) {
case 'k':
$target *= 1000;
break;
case 'ki':
$target *= 1024;
break;
case 'm':
$target *= 1000000;
break;
case 'mi':
$target *= 1024 * 1024;
break;
case 'g':
$target *= 1000000000;
break;
case 'gi':
$target *= 1024 * 1024 * 1024;
break;
}
}
$this->setTarget($target);
$this->setOperator(isset($matches[1]) ? $matches[1] : '==');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* Comparator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Comparator
{
private $target;
private $operator = '==';
/**
* Gets the target value.
*
* @return string The target value
*/
public function getTarget()
{
return $this->target;
}
/**
* Sets the target value.
*
* @param string $target The target value
*/
public function setTarget($target)
{
$this->target = $target;
}
/**
* Gets the comparison operator.
*
* @return string The operator
*/
public function getOperator()
{
return $this->operator;
}
/**
* Sets the comparison operator.
*
* @param string $operator A valid operator
*
* @throws \InvalidArgumentException
*/
public function setOperator($operator)
{
if (!$operator) {
$operator = '==';
}
if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) {
throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator));
}
$this->operator = $operator;
}
/**
* Tests against the target.
*
* @param mixed $test A test value
*
* @return bool
*/
public function test($test)
{
switch ($this->operator) {
case '>':
return $test > $this->target;
case '>=':
return $test >= $this->target;
case '<':
return $test < $this->target;
case '<=':
return $test <= $this->target;
case '!=':
return $test != $this->target;
}
return $test == $this->target;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* DateCompare compiles date comparisons.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DateComparator extends Comparator
{
/**
* @param string $test A comparison string
*
* @throws \InvalidArgumentException If the test is not understood
*/
public function __construct(string $test)
{
if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) {
throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test));
}
try {
$date = new \DateTime($matches[2]);
$target = $date->format('U');
} catch (\Exception $e) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2]));
}
$operator = isset($matches[1]) ? $matches[1] : '==';
if ('since' === $operator || 'after' === $operator) {
$operator = '>';
}
if ('until' === $operator || 'before' === $operator) {
$operator = '<';
}
$this->setOperator($operator);
$this->setTarget($target);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
use Symfony\Component\Finder\Comparator\DateComparator;
use Symfony\Component\Finder\Comparator\NumberComparator;
use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
use Symfony\Component\Finder\Iterator\CustomFilterIterator;
use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
use Symfony\Component\Finder\Iterator\SortableIterator;
/**
* Finder allows to build rules to find files and directories.
*
* It is a thin wrapper around several specialized iterator classes.
*
* All rules may be invoked several times.
*
* All methods return the current Finder object to allow chaining:
*
* $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Finder implements \IteratorAggregate, \Countable
{
const IGNORE_VCS_FILES = 1;
const IGNORE_DOT_FILES = 2;
const IGNORE_VCS_IGNORED_FILES = 4;
private $mode = 0;
private $names = [];
private $notNames = [];
private $exclude = [];
private $filters = [];
private $depths = [];
private $sizes = [];
private $followLinks = false;
private $reverseSorting = false;
private $sort = false;
private $ignore = 0;
private $dirs = [];
private $dates = [];
private $iterators = [];
private $contains = [];
private $notContains = [];
private $paths = [];
private $notPaths = [];
private $ignoreUnreadableDirs = false;
private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'];
public function __construct()
{
$this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
}
/**
* Creates a new Finder.
*
* @return static
*/
public static function create()
{
return new static();
}
/**
* Restricts the matching to directories only.
*
* @return $this
*/
public function directories()
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;
return $this;
}
/**
* Restricts the matching to files only.
*
* @return $this
*/
public function files()
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
return $this;
}
/**
* Adds tests for the directory depth.
*
* Usage:
*
* $finder->depth('> 1') // the Finder will start matching at level 1.
* $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
* $finder->depth(['>= 1', '< 3'])
*
* @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels
*
* @return $this
*
* @see DepthRangeFilterIterator
* @see NumberComparator
*/
public function depth($levels)
{
foreach ((array) $levels as $level) {
$this->depths[] = new Comparator\NumberComparator($level);
}
return $this;
}
/**
* Adds tests for file dates (last modified).
*
* The date must be something that strtotime() is able to parse:
*
* $finder->date('since yesterday');
* $finder->date('until 2 days ago');
* $finder->date('> now - 2 hours');
* $finder->date('>= 2005-10-15');
* $finder->date(['>= 2005-10-15', '<= 2006-05-27']);
*
* @param string|string[] $dates A date range string or an array of date ranges
*
* @return $this
*
* @see strtotime
* @see DateRangeFilterIterator
* @see DateComparator
*/
public function date($dates)
{
foreach ((array) $dates as $date) {
$this->dates[] = new Comparator\DateComparator($date);
}
return $this;
}
/**
* Adds rules that files must match.
*
* You can use patterns (delimited with / sign), globs or simple strings.
*
* $finder->name('*.php')
* $finder->name('/\.php$/') // same as above
* $finder->name('test.php')
* $finder->name(['test.py', 'test.php'])
*
* @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function name($patterns)
{
$this->names = array_merge($this->names, (array) $patterns);
return $this;
}
/**
* Adds rules that files must not match.
*
* @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function notName($patterns)
{
$this->notNames = array_merge($this->notNames, (array) $patterns);
return $this;
}
/**
* Adds tests that file contents must match.
*
* Strings or PCRE patterns can be used:
*
* $finder->contains('Lorem ipsum')
* $finder->contains('/Lorem ipsum/i')
* $finder->contains(['dolor', '/ipsum/i'])
*
* @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
*
* @return $this
*
* @see FilecontentFilterIterator
*/
public function contains($patterns)
{
$this->contains = array_merge($this->contains, (array) $patterns);
return $this;
}
/**
* Adds tests that file contents must not match.
*
* Strings or PCRE patterns can be used:
*
* $finder->notContains('Lorem ipsum')
* $finder->notContains('/Lorem ipsum/i')
* $finder->notContains(['lorem', '/dolor/i'])
*
* @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
*
* @return $this
*
* @see FilecontentFilterIterator
*/
public function notContains($patterns)
{
$this->notContains = array_merge($this->notContains, (array) $patterns);
return $this;
}
/**
* Adds rules that filenames must match.
*
* You can use patterns (delimited with / sign) or simple strings.
*
* $finder->path('some/special/dir')
* $finder->path('/some\/special\/dir/') // same as above
* $finder->path(['some dir', 'another/dir'])
*
* Use only / as dirname separator.
*
* @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function path($patterns)
{
$this->paths = array_merge($this->paths, (array) $patterns);
return $this;
}
/**
* Adds rules that filenames must not match.
*
* You can use patterns (delimited with / sign) or simple strings.
*
* $finder->notPath('some/special/dir')
* $finder->notPath('/some\/special\/dir/') // same as above
* $finder->notPath(['some/file.txt', 'another/file.log'])
*
* Use only / as dirname separator.
*
* @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function notPath($patterns)
{
$this->notPaths = array_merge($this->notPaths, (array) $patterns);
return $this;
}
/**
* Adds tests for file sizes.
*
* $finder->size('> 10K');
* $finder->size('<= 1Ki');
* $finder->size(4);
* $finder->size(['> 10K', '< 20K'])
*
* @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges
*
* @return $this
*
* @see SizeRangeFilterIterator
* @see NumberComparator
*/
public function size($sizes)
{
foreach ((array) $sizes as $size) {
$this->sizes[] = new Comparator\NumberComparator($size);
}
return $this;
}
/**
* Excludes directories.
*
* Directories passed as argument must be relative to the ones defined with the `in()` method. For example:
*
* $finder->in(__DIR__)->exclude('ruby');
*
* @param string|array $dirs A directory path or an array of directories
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function exclude($dirs)
{
$this->exclude = array_merge($this->exclude, (array) $dirs);
return $this;
}
/**
* Excludes "hidden" directories and files (starting with a dot).
*
* This option is enabled by default.
*
* @param bool $ignoreDotFiles Whether to exclude "hidden" files or not
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function ignoreDotFiles($ignoreDotFiles)
{
if ($ignoreDotFiles) {
$this->ignore |= static::IGNORE_DOT_FILES;
} else {
$this->ignore &= ~static::IGNORE_DOT_FILES;
}
return $this;
}
/**
* Forces the finder to ignore version control directories.
*
* This option is enabled by default.
*
* @param bool $ignoreVCS Whether to exclude VCS files or not
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function ignoreVCS($ignoreVCS)
{
if ($ignoreVCS) {
$this->ignore |= static::IGNORE_VCS_FILES;
} else {
$this->ignore &= ~static::IGNORE_VCS_FILES;
}
return $this;
}
/**
* Forces Finder to obey .gitignore and ignore files based on rules listed there.
*
* This option is disabled by default.
*
* @return $this
*/
public function ignoreVCSIgnored(bool $ignoreVCSIgnored)
{
if ($ignoreVCSIgnored) {
$this->ignore |= static::IGNORE_VCS_IGNORED_FILES;
} else {
$this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES;
}
return $this;
}
/**
* Adds VCS patterns.
*
* @see ignoreVCS()
*
* @param string|string[] $pattern VCS patterns to ignore
*/
public static function addVCSPattern($pattern)
{
foreach ((array) $pattern as $p) {
self::$vcsPatterns[] = $p;
}
self::$vcsPatterns = array_unique(self::$vcsPatterns);
}
/**
* Sorts files and directories by an anonymous function.
*
* The anonymous function receives two \SplFileInfo instances to compare.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sort(\Closure $closure)
{
$this->sort = $closure;
return $this;
}
/**
* Sorts files and directories by name.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @param bool $useNaturalSort Whether to use natural sort or not, disabled by default
*
* @return $this
*
* @see SortableIterator
*/
public function sortByName(/* bool $useNaturalSort = false */)
{
if (\func_num_args() < 1 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface) {
@trigger_error(sprintf('The "%s()" method will have a new "bool $useNaturalSort = false" argument in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
}
$useNaturalSort = 0 < \func_num_args() && func_get_arg(0);
$this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME;
return $this;
}
/**
* Sorts files and directories by type (directories before files), then by name.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByType()
{
$this->sort = Iterator\SortableIterator::SORT_BY_TYPE;
return $this;
}
/**
* Sorts files and directories by the last accessed time.
*
* This is the time that the file was last accessed, read or written to.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByAccessedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME;
return $this;
}
/**
* Reverses the sorting.
*
* @return $this
*/
public function reverseSorting()
{
$this->reverseSorting = true;
return $this;
}
/**
* Sorts files and directories by the last inode changed time.
*
* This is the time that the inode information was last modified (permissions, owner, group or other metadata).
*
* On Windows, since inode is not available, changed time is actually the file creation time.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByChangedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME;
return $this;
}
/**
* Sorts files and directories by the last modified time.
*
* This is the last time the actual contents of the file were last modified.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByModifiedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME;
return $this;
}
/**
* Filters the iterator with an anonymous function.
*
* The anonymous function receives a \SplFileInfo and must return false
* to remove files.
*
* @return $this
*
* @see CustomFilterIterator
*/
public function filter(\Closure $closure)
{
$this->filters[] = $closure;
return $this;
}
/**
* Forces the following of symlinks.
*
* @return $this
*/
public function followLinks()
{
$this->followLinks = true;
return $this;
}
/**
* Tells finder to ignore unreadable directories.
*
* By default, scanning unreadable directories content throws an AccessDeniedException.
*
* @param bool $ignore
*
* @return $this
*/
public function ignoreUnreadableDirs($ignore = true)
{
$this->ignoreUnreadableDirs = (bool) $ignore;
return $this;
}
/**
* Searches files and directories which match defined rules.
*
* @param string|string[] $dirs A directory path or an array of directories
*
* @return $this
*
* @throws DirectoryNotFoundException if one of the directories does not exist
*/
public function in($dirs)
{
$resolvedDirs = [];
foreach ((array) $dirs as $dir) {
if (is_dir($dir)) {
$resolvedDirs[] = $this->normalizeDir($dir);
} elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR | GLOB_NOSORT)) {
sort($glob);
$resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $glob));
} else {
throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir));
}
}
$this->dirs = array_merge($this->dirs, $resolvedDirs);
return $this;
}
/**
* Returns an Iterator for the current Finder configuration.
*
* This method implements the IteratorAggregate interface.
*
* @return \Iterator|SplFileInfo[] An iterator
*
* @throws \LogicException if the in() method has not been called
*/
public function getIterator()
{
if (0 === \count($this->dirs) && 0 === \count($this->iterators)) {
throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
}
if (1 === \count($this->dirs) && 0 === \count($this->iterators)) {
return $this->searchInDirectory($this->dirs[0]);
}
$iterator = new \AppendIterator();
foreach ($this->dirs as $dir) {
$iterator->append($this->searchInDirectory($dir));
}
foreach ($this->iterators as $it) {
$iterator->append($it);
}
return $iterator;
}
/**
* Appends an existing set of files/directories to the finder.
*
* The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
*
* @param iterable $iterator
*
* @return $this
*
* @throws \InvalidArgumentException when the given argument is not iterable
*/
public function append($iterator)
{
if ($iterator instanceof \IteratorAggregate) {
$this->iterators[] = $iterator->getIterator();
} elseif ($iterator instanceof \Iterator) {
$this->iterators[] = $iterator;
} elseif ($iterator instanceof \Traversable || \is_array($iterator)) {
$it = new \ArrayIterator();
foreach ($iterator as $file) {
$it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file));
}
$this->iterators[] = $it;
} else {
throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
}
return $this;
}
/**
* Check if the any results were found.
*
* @return bool
*/
public function hasResults()
{
foreach ($this->getIterator() as $_) {
return true;
}
return false;
}
/**
* Counts all the results collected by the iterators.
*
* @return int
*/
public function count()
{
return iterator_count($this->getIterator());
}
private function searchInDirectory(string $dir): \Iterator
{
$exclude = $this->exclude;
$notPaths = $this->notPaths;
if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
$exclude = array_merge($exclude, self::$vcsPatterns);
}
if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
$notPaths[] = '#(^|/)\..+(/|$)#';
}
if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
$gitignoreFilePath = sprintf('%s/.gitignore', $dir);
if (!is_readable($gitignoreFilePath)) {
throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath));
}
$notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]);
}
$minDepth = 0;
$maxDepth = PHP_INT_MAX;
foreach ($this->depths as $comparator) {
switch ($comparator->getOperator()) {
case '>':
$minDepth = $comparator->getTarget() + 1;
break;
case '>=':
$minDepth = $comparator->getTarget();
break;
case '<':
$maxDepth = $comparator->getTarget() - 1;
break;
case '<=':
$maxDepth = $comparator->getTarget();
break;
default:
$minDepth = $maxDepth = $comparator->getTarget();
}
}
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
if ($this->followLinks) {
$flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
}
$iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);
if ($exclude) {
$iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude);
}
$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
if ($minDepth > 0 || $maxDepth < PHP_INT_MAX) {
$iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth);
}
if ($this->mode) {
$iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
}
if ($this->names || $this->notNames) {
$iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
}
if ($this->contains || $this->notContains) {
$iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
}
if ($this->sizes) {
$iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
}
if ($this->dates) {
$iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates);
}
if ($this->filters) {
$iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
}
if ($this->paths || $notPaths) {
$iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
}
if ($this->sort || $this->reverseSorting) {
$iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting);
$iterator = $iteratorAggregate->getIterator();
}
return $iterator;
}
/**
* Normalizes given directory names by removing trailing slashes.
*
* Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper
*/
private function normalizeDir(string $dir): string
{
$dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR);
if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
$dir .= '/';
}
return $dir;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Glob matches globbing patterns against text.
*
* if match_glob("foo.*", "foo.bar") echo "matched\n";
*
* // prints foo.bar and foo.baz
* $regex = glob_to_regex("foo.*");
* for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t)
* {
* if (/$regex/) echo "matched: $car\n";
* }
*
* Glob implements glob(3) style matching that can be used to match
* against text, rather than fetching names from a filesystem.
*
* Based on the Perl Text::Glob module.
*
* @author Fabien Potencier <fabien@symfony.com> PHP port
* @author Richard Clamp <richardc@unixbeard.net> Perl version
* @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
* @copyright 2002 Richard Clamp <richardc@unixbeard.net>
*/
class Glob
{
/**
* Returns a regexp which is the equivalent of the glob pattern.
*
* @param string $glob The glob pattern
* @param bool $strictLeadingDot
* @param bool $strictWildcardSlash
* @param string $delimiter Optional delimiter
*
* @return string regex The regexp
*/
public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true, $delimiter = '#')
{
$firstByte = true;
$escaping = false;
$inCurlies = 0;
$regex = '';
$sizeGlob = \strlen($glob);
for ($i = 0; $i < $sizeGlob; ++$i) {
$car = $glob[$i];
if ($firstByte && $strictLeadingDot && '.' !== $car) {
$regex .= '(?=[^\.])';
}
$firstByte = '/' === $car;
if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) {
$car = '[^/]++/';
if (!isset($glob[$i + 3])) {
$car .= '?';
}
if ($strictLeadingDot) {
$car = '(?=[^\.])'.$car;
}
$car = '/(?:'.$car.')*';
$i += 2 + isset($glob[$i + 3]);
if ('/' === $delimiter) {
$car = str_replace('/', '\\/', $car);
}
}
if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) {
$regex .= "\\$car";
} elseif ('*' === $car) {
$regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*');
} elseif ('?' === $car) {
$regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.');
} elseif ('{' === $car) {
$regex .= $escaping ? '\\{' : '(';
if (!$escaping) {
++$inCurlies;
}
} elseif ('}' === $car && $inCurlies) {
$regex .= $escaping ? '}' : ')';
if (!$escaping) {
--$inCurlies;
}
} elseif (',' === $car && $inCurlies) {
$regex .= $escaping ? ',' : '|';
} elseif ('\\' === $car) {
if ($escaping) {
$regex .= '\\\\';
$escaping = false;
} else {
$escaping = true;
}
continue;
} else {
$regex .= $car;
}
$escaping = false;
}
return $delimiter.'^'.$regex.'$'.$delimiter;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author WÅodzimierz Gajda <gajdaw@gajdaw.pl>
*/
class FilecontentFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
if (!$this->matchRegexps && !$this->noMatchRegexps) {
return true;
}
$fileinfo = $this->current();
if ($fileinfo->isDir() || !$fileinfo->isReadable()) {
return false;
}
$content = $fileinfo->getContents();
if (!$content) {
return false;
}
return $this->isAccepted($content);
}
/**
* Converts string to regexp if necessary.
*
* @param string $str Pattern: string or regexp
*
* @return string regexp corresponding to a given string or regexp
*/
protected function toRegex($str)
{
return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class MultiplePcreFilterIterator extends \FilterIterator
{
protected $matchRegexps = [];
protected $noMatchRegexps = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param array $matchPatterns An array of patterns that need to match
* @param array $noMatchPatterns An array of patterns that need to not match
*/
public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns)
{
foreach ($matchPatterns as $pattern) {
$this->matchRegexps[] = $this->toRegex($pattern);
}
foreach ($noMatchPatterns as $pattern) {
$this->noMatchRegexps[] = $this->toRegex($pattern);
}
parent::__construct($iterator);
}
/**
* Checks whether the string is accepted by the regex filters.
*
* If there is no regexps defined in the class, this method will accept the string.
* Such case can be handled by child classes before calling the method if they want to
* apply a different behavior.
*
* @param string $string The string to be matched against filters
*
* @return bool
*/
protected function isAccepted($string)
{
// should at least not match one rule to exclude
foreach ($this->noMatchRegexps as $regex) {
if (preg_match($regex, $string)) {
return false;
}
}
// should at least match one rule
if ($this->matchRegexps) {
foreach ($this->matchRegexps as $regex) {
if (preg_match($regex, $string)) {
return true;
}
}
return false;
}
// If there is no match rules, the file is accepted
return true;
}
/**
* Checks whether the string is a regex.
*
* @param string $str
*
* @return bool Whether the given string is a regex
*/
protected function isRegex($str)
{
if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) {
$start = substr($m[1], 0, 1);
$end = substr($m[1], -1);
if ($start === $end) {
return !preg_match('/[*?[:alnum:] \\\\]/', $start);
}
foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) {
if ($start === $delimiters[0] && $end === $delimiters[1]) {
return true;
}
}
}
return false;
}
/**
* Converts string into regexp.
*
* @param string $str Pattern
*
* @return string regexp corresponding to a given string
*/
abstract protected function toRegex($str);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Comparator\NumberComparator;
/**
* SizeRangeFilterIterator filters out files that are not in the given size range.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SizeRangeFilterIterator extends \FilterIterator
{
private $comparators = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param NumberComparator[] $comparators An array of NumberComparator instances
*/
public function __construct(\Iterator $iterator, array $comparators)
{
$this->comparators = $comparators;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
if (!$fileinfo->isFile()) {
return true;
}
$filesize = $fileinfo->getSize();
foreach ($this->comparators as $compare) {
if (!$compare->test($filesize)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Glob;
/**
* FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FilenameFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
return $this->isAccepted($this->current()->getFilename());
}
/**
* Converts glob to regexp.
*
* PCRE patterns are left unchanged.
* Glob strings are transformed with Glob::toRegex().
*
* @param string $str Pattern: glob or regexp
*
* @return string regexp corresponding to a given glob or regexp
*/
protected function toRegex($str)
{
return $this->isRegex($str) ? $str : Glob::toRegex($str);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Comparator\DateComparator;
/**
* DateRangeFilterIterator filters out files that are not in the given date range (last modified dates).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DateRangeFilterIterator extends \FilterIterator
{
private $comparators = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param DateComparator[] $comparators An array of DateComparator instances
*/
public function __construct(\Iterator $iterator, array $comparators)
{
$this->comparators = $comparators;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
if (!file_exists($fileinfo->getPathname())) {
return false;
}
$filedate = $fileinfo->getMTime();
foreach ($this->comparators as $compare) {
if (!$compare->test($filedate)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* CustomFilterIterator filters files by applying anonymous functions.
*
* The anonymous function receives a \SplFileInfo and must return false
* to remove files.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class CustomFilterIterator extends \FilterIterator
{
private $filters = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param callable[] $filters An array of PHP callbacks
*
* @throws \InvalidArgumentException
*/
public function __construct(\Iterator $iterator, array $filters)
{
foreach ($filters as $filter) {
if (!\is_callable($filter)) {
throw new \InvalidArgumentException('Invalid PHP callback.');
}
}
$this->filters = $filters;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
foreach ($this->filters as $filter) {
if (false === $filter($fileinfo)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Finder\SplFileInfo;
/**
* Extends the \RecursiveDirectoryIterator to support relative paths.
*
* @author Victor Berchet <victor@suumit.com>
*/
class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
{
/**
* @var bool
*/
private $ignoreUnreadableDirs;
/**
* @var bool
*/
private $rewindable;
// these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
private $rootPath;
private $subPath;
private $directorySeparator = '/';
/**
* @throws \RuntimeException
*/
public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false)
{
if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
throw new \RuntimeException('This iterator only support returning current as fileinfo.');
}
parent::__construct($path, $flags);
$this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
$this->rootPath = $path;
if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
$this->directorySeparator = \DIRECTORY_SEPARATOR;
}
}
/**
* Return an instance of SplFileInfo with support for relative paths.
*
* @return SplFileInfo File information
*/
public function current()
{
// the logic here avoids redoing the same work in all iterations
if (null === $subPathname = $this->subPath) {
$subPathname = $this->subPath = (string) $this->getSubPath();
}
if ('' !== $subPathname) {
$subPathname .= $this->directorySeparator;
}
$subPathname .= $this->getFilename();
return new SplFileInfo($this->rootPath.$this->directorySeparator.$subPathname, $this->subPath, $subPathname);
}
/**
* @return \RecursiveIterator
*
* @throws AccessDeniedException
*/
public function getChildren()
{
try {
$children = parent::getChildren();
if ($children instanceof self) {
// parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
$children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;
// performance optimization to avoid redoing the same work in all children
$children->rewindable = &$this->rewindable;
$children->rootPath = $this->rootPath;
}
return $children;
} catch (\UnexpectedValueException $e) {
if ($this->ignoreUnreadableDirs) {
// If directory is unreadable and finder is set to ignore it, a fake empty content is returned.
return new \RecursiveArrayIterator([]);
} else {
throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
}
}
}
/**
* Do nothing for non rewindable stream.
*/
public function rewind()
{
if (false === $this->isRewindable()) {
return;
}
parent::rewind();
}
/**
* Checks if the stream is rewindable.
*
* @return bool true when the stream is rewindable, false otherwise
*/
public function isRewindable()
{
if (null !== $this->rewindable) {
return $this->rewindable;
}
if (false !== $stream = @opendir($this->getPath())) {
$infos = stream_get_meta_data($stream);
closedir($stream);
if ($infos['seekable']) {
return $this->rewindable = true;
}
}
return $this->rewindable = false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* ExcludeDirectoryFilterIterator filters out directories.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator
{
private $iterator;
private $isRecursive;
private $excludedDirs = [];
private $excludedPattern;
/**
* @param \Iterator $iterator The Iterator to filter
* @param string[] $directories An array of directories to exclude
*/
public function __construct(\Iterator $iterator, array $directories)
{
$this->iterator = $iterator;
$this->isRecursive = $iterator instanceof \RecursiveIterator;
$patterns = [];
foreach ($directories as $directory) {
$directory = rtrim($directory, '/');
if (!$this->isRecursive || false !== strpos($directory, '/')) {
$patterns[] = preg_quote($directory, '#');
} else {
$this->excludedDirs[$directory] = true;
}
}
if ($patterns) {
$this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#';
}
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool True if the value should be kept, false otherwise
*/
public function accept()
{
if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) {
return false;
}
if ($this->excludedPattern) {
$path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
$path = str_replace('\\', '/', $path);
return !preg_match($this->excludedPattern, $path);
}
return true;
}
/**
* @return bool
*/
public function hasChildren()
{
return $this->isRecursive && $this->iterator->hasChildren();
}
public function getChildren()
{
$children = new self($this->iterator->getChildren(), []);
$children->excludedDirs = $this->excludedDirs;
$children->excludedPattern = $this->excludedPattern;
return $children;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* SortableIterator applies a sort on a given Iterator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SortableIterator implements \IteratorAggregate
{
const SORT_BY_NONE = 0;
const SORT_BY_NAME = 1;
const SORT_BY_TYPE = 2;
const SORT_BY_ACCESSED_TIME = 3;
const SORT_BY_CHANGED_TIME = 4;
const SORT_BY_MODIFIED_TIME = 5;
const SORT_BY_NAME_NATURAL = 6;
private $iterator;
private $sort;
/**
* @param \Traversable $iterator The Iterator to filter
* @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
*
* @throws \InvalidArgumentException
*/
public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false)
{
$this->iterator = $iterator;
$order = $reverseOrder ? -1 : 1;
if (self::SORT_BY_NAME === $sort) {
$this->sort = static function ($a, $b) use ($order) {
return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_NAME_NATURAL === $sort) {
$this->sort = static function ($a, $b) use ($order) {
return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_TYPE === $sort) {
$this->sort = static function ($a, $b) use ($order) {
if ($a->isDir() && $b->isFile()) {
return -$order;
} elseif ($a->isFile() && $b->isDir()) {
return $order;
}
return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
$this->sort = static function ($a, $b) use ($order) {
return $order * ($a->getATime() - $b->getATime());
};
} elseif (self::SORT_BY_CHANGED_TIME === $sort) {
$this->sort = static function ($a, $b) use ($order) {
return $order * ($a->getCTime() - $b->getCTime());
};
} elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
$this->sort = static function ($a, $b) use ($order) {
return $order * ($a->getMTime() - $b->getMTime());
};
} elseif (self::SORT_BY_NONE === $sort) {
$this->sort = $order;
} elseif (\is_callable($sort)) {
$this->sort = $reverseOrder ? static function ($a, $b) use ($sort) { return -$sort($a, $b); } : $sort;
} else {
throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
}
}
/**
* @return \Traversable
*/
public function getIterator()
{
if (1 === $this->sort) {
return $this->iterator;
}
$array = iterator_to_array($this->iterator, true);
if (-1 === $this->sort) {
$array = array_reverse($array);
} else {
uasort($array, $this->sort);
}
return new \ArrayIterator($array);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* DepthRangeFilterIterator limits the directory depth.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DepthRangeFilterIterator extends \FilterIterator
{
private $minDepth = 0;
/**
* @param \RecursiveIteratorIterator $iterator The Iterator to filter
* @param int $minDepth The min depth
* @param int $maxDepth The max depth
*/
public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = PHP_INT_MAX)
{
$this->minDepth = $minDepth;
$iterator->setMaxDepth(PHP_INT_MAX === $maxDepth ? -1 : $maxDepth);
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
return $this->getInnerIterator()->getDepth() >= $this->minDepth;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* FileTypeFilterIterator only keeps files, directories, or both.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileTypeFilterIterator extends \FilterIterator
{
const ONLY_FILES = 1;
const ONLY_DIRECTORIES = 2;
private $mode;
/**
* @param \Iterator $iterator The Iterator to filter
* @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
*/
public function __construct(\Iterator $iterator, int $mode)
{
$this->mode = $mode;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) {
return false;
} elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) {
return false;
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* PathFilterIterator filters files by path patterns (e.g. some/special/dir).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author WÅodzimierz Gajda <gajdaw@gajdaw.pl>
*/
class PathFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$filename = $this->current()->getRelativePathname();
if ('\\' === \DIRECTORY_SEPARATOR) {
$filename = str_replace('\\', '/', $filename);
}
return $this->isAccepted($filename);
}
/**
* Converts strings to regexp.
*
* PCRE patterns are left unchanged.
*
* Default conversion:
* 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/'
*
* Use only / as directory separator (on Windows also).
*
* @param string $str Pattern: regexp or dirname
*
* @return string regexp corresponding to a given string or regexp
*/
protected function toRegex($str)
{
return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Gitignore matches against text.
*
* @author Ahmed Abdou <mail@ahmd.io>
*/
class Gitignore
{
/**
* Returns a regexp which is the equivalent of the gitignore pattern.
*
* @return string The regexp
*/
public static function toRegex(string $gitignoreFileContent): string
{
$gitignoreFileContent = preg_replace('/^[^\\\r\n]*#.*/m', '', $gitignoreFileContent);
$gitignoreLines = preg_split('/\r\n|\r|\n/', $gitignoreFileContent);
$gitignoreLines = array_map('trim', $gitignoreLines);
$gitignoreLines = array_filter($gitignoreLines);
$ignoreLinesPositive = array_filter($gitignoreLines, function (string $line) {
return !preg_match('/^!/', $line);
});
$ignoreLinesNegative = array_filter($gitignoreLines, function (string $line) {
return preg_match('/^!/', $line);
});
$ignoreLinesNegative = array_map(function (string $line) {
return preg_replace('/^!(.*)/', '${1}', $line);
}, $ignoreLinesNegative);
$ignoreLinesNegative = array_map([__CLASS__, 'getRegexFromGitignore'], $ignoreLinesNegative);
$ignoreLinesPositive = array_map([__CLASS__, 'getRegexFromGitignore'], $ignoreLinesPositive);
if (empty($ignoreLinesPositive)) {
return '/^$/';
}
if (empty($ignoreLinesNegative)) {
return sprintf('/%s/', implode('|', $ignoreLinesPositive));
}
return sprintf('/(?=^(?:(?!(%s)).)*$)(%s)/', implode('|', $ignoreLinesNegative), implode('|', $ignoreLinesPositive));
}
private static function getRegexFromGitignore(string $gitignorePattern): string
{
$regex = '(';
if (0 === strpos($gitignorePattern, '/')) {
$gitignorePattern = substr($gitignorePattern, 1);
$regex .= '^';
} else {
$regex .= '(^|\/)';
}
if ('/' === $gitignorePattern[\strlen($gitignorePattern) - 1]) {
$gitignorePattern = substr($gitignorePattern, 0, -1);
}
$iMax = \strlen($gitignorePattern);
for ($i = 0; $i < $iMax; ++$i) {
$doubleChars = substr($gitignorePattern, $i, 2);
if ('**' === $doubleChars) {
$regex .= '.+';
++$i;
continue;
}
$c = $gitignorePattern[$i];
switch ($c) {
case '*':
$regex .= '[^\/]+';
break;
case '/':
case '.':
case ':':
case '(':
case ')':
case '{':
case '}':
$regex .= '\\'.$c;
break;
default:
$regex .= $c;
}
}
$regex .= '($|\/)';
$regex .= ')';
return $regex;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Exception;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*/
class AccessDeniedException extends \UnexpectedValueException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Exception;
/**
* @author Andreas Erhard <andreas.erhard@i-med.ac.at>
*/
class DirectoryNotFoundException extends \InvalidArgumentException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Tag\TaggedValue;
/**
* Parser parses YAML strings to convert them to PHP arrays.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class Parser
{
const TAG_PATTERN = '(?P<tag>![\w!.\/:-]+)';
const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
private $filename;
private $offset = 0;
private $totalNumberOfLines;
private $lines = [];
private $currentLineNb = -1;
private $currentLine = '';
private $refs = [];
private $skippedLineNumbers = [];
private $locallySkippedLineNumbers = [];
private $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
*
* @return mixed The YAML converted to a PHP value
*
* @throws ParseException If the file could not be read or the YAML is not valid
*/
public function parseFile(string $filename, int $flags = 0)
{
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
*
* @return mixed A PHP value
*
* @throws ParseException If the YAML is not valid
*/
public function parse(string $value, int $flags = 0)
{
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 = [];
$mbEncoding = null;
if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('UTF-8');
}
try {
$data = $this->doParse($value, $flags);
} finally {
if (null !== $mbEncoding) {
mb_internal_encoding($mbEncoding);
}
$this->lines = [];
$this->currentLine = '';
$this->refs = [];
$this->skippedLineNumbers = [];
$this->locallySkippedLineNumbers = [];
}
return $data;
}
private function doParse(string $value, int $flags)
{
$this->currentLineNb = -1;
$this->currentLine = '';
$value = $this->cleanup($value);
$this->lines = explode("\n", $value);
$this->locallySkippedLineNumbers = [];
if (null === $this->totalNumberOfLines) {
$this->totalNumberOfLines = \count($this->lines);
}
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<leadspaces>\s+)(?P<value>.+))?$#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('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $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']) || '' == trim($values['value'], ' ') || 0 === strpos(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'])
&& self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\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<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
&& (false === strpos($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(sprintf('%s keys are not supported. Quote your evaluable mapping keys instead.', is_numeric($key) ? 'Numeric' : 'Non-string'), $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<ref>[^ ]+)#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, %s] detected for reference "%s".', implode(', ', \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('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $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'] || 0 === strpos($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->parseQuotedString($this->currentLine), $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($this->currentLine), $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($this->currentLine), $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) {
if ('' !== ltrim($line) && '#' === ltrim($line)[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 (false !== strpos($line, ': ')) {
@trigger_error('Support for mapping keys in multi-line blocks is deprecated since Symfony 4.3 and will throw a ParseException in 5.0.', E_USER_DEPRECATED);
}
if ('' === trim($line)) {
$value .= "\n";
} elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
$value .= ' ';
}
if ('' !== trim($line) && '\\' === substr($line, -1)) {
$value .= ltrim(substr($line, 0, -1));
} elseif ('' !== trim($line)) {
$value .= trim($line);
}
if ('' === trim($line)) {
$previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false;
} elseif ('\\' === substr($line, -1)) {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = true;
} else {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = false;
}
}
try {
return Inline::parse(trim($value));
} catch (ParseException $e) {
// 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 && !\is_object($data) && 'mapping' === $context) {
$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
*
* @return int The current line number
*/
public function getRealCurrentLineNb(): int
{
$realCurrentLineNumber = $this->currentLineNb + $this->offset;
foreach ($this->skippedLineNumbers as $skippedLineNumber) {
if ($skippedLineNumber > $realCurrentLineNumber) {
break;
}
++$realCurrentLineNumber;
}
return $realCurrentLineNumber;
}
/**
* Returns the current line indentation.
*
* @return int The current line indentation
*/
private function getCurrentLineIndentation(): int
{
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
*
* @return string A YAML string
*
* @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);
} 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();
while ($this->moveToNextLine()) {
$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);
}
/**
* Moves the parser to the next line.
*/
private function moveToNextLine(): bool
{
if ($this->currentLineNb >= \count($this->lines) - 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)
*
* @return mixed A PHP value
*
* @throws ParseException When reference does not exist
*/
private function parseValue(string $value, int $flags, string $context)
{
if (0 === strpos($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, %s] detected for reference "%s".', implode(', ', \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 = isset($matches['modifiers']) ? $matches['modifiers'] : '';
$data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) 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]) {
return Inline::parse($this->lexInlineMapping($value), $flags, $this->refs);
} elseif ('' !== $value && '[' === $value[0]) {
return Inline::parse($this->lexInlineSequence($value), $flags, $this->refs);
}
$quotation = '' !== $value && ('"' === $value[0] || "'" === $value[0]) ? $value[0] : null;
// do not take following lines into account when the current line is a quoted single line value
if (null !== $quotation && self::preg_match('/^'.$quotation.'.*'.$quotation.'(\s*#.*)?$/', $value)) {
return Inline::parse($value, $flags, $this->refs);
}
$lines = [];
while ($this->moveToNextLine()) {
// unquoted strings end before the first unindented line
if (null === $quotation && 0 === $this->getCurrentLineIndentation()) {
$this->moveToPreviousLine();
break;
}
$lines[] = trim($this->currentLine);
// quoted string values end with a line that is terminated with the quotation character
if ('' !== $this->currentLine && substr($this->currentLine, -1) === $quotation) {
break;
}
}
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] && false !== strpos($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.
*
* @return bool Returns true if the next line is indented, false otherwise
*/
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;
}
/**
* Returns true if the current line is blank or if it is a comment line.
*
* @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
*/
private function isCurrentLineEmpty(): bool
{
return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
}
/**
* Returns true if the current line is blank.
*
* @return bool Returns true if the current line is blank, false otherwise
*/
private function isCurrentLineBlank(): bool
{
return '' == trim($this->currentLine, ' ');
}
/**
* Returns true if the current line is a comment line.
*
* @return bool Returns true if the current line is a comment line, false otherwise
*/
private function isCurrentLineComment(): bool
{
//checking explicitly the first char of the trim is faster than loops or strpos
$ltrimmedLine = ltrim($this->currentLine, ' ');
return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
}
private function isCurrentLineLastLineInDocument(): bool
{
return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
}
/**
* Cleanups a YAML string to be parsed.
*
* @param string $value The input YAML string
*
* @return string A cleaned up YAML string
*/
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;
}
/**
* Returns true if the next line starts unindented collection.
*
* @return bool Returns true if the next line starts unindented collection, false otherwise
*/
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;
}
/**
* Returns true if the string is un-indented collection item.
*
* @return bool Returns true if the string is un-indented collection item, false otherwise
*/
private function isStringUnIndentedCollectionItem(): bool
{
return '-' === rtrim($this->currentLine) || 0 === strpos($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)) {
switch (preg_last_error()) {
case PREG_INTERNAL_ERROR:
$error = 'Internal PCRE error.';
break;
case PREG_BACKTRACK_LIMIT_ERROR:
$error = 'pcre.backtrack_limit reached.';
break;
case PREG_RECURSION_LIMIT_ERROR:
$error = 'pcre.recursion_limit reached.';
break;
case PREG_BAD_UTF8_ERROR:
$error = 'Malformed UTF-8 data.';
break;
case PREG_BAD_UTF8_OFFSET_ERROR:
$error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
break;
default:
$error = '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 parseQuotedString(string $yaml): ?string
{
if ('' === $yaml || ('"' !== $yaml[0] && "'" !== $yaml[0])) {
throw new \InvalidArgumentException(sprintf('"%s" is not a quoted string.', $yaml));
}
$lines = [$yaml];
while ($this->moveToNextLine()) {
$lines[] = $this->currentLine;
if (!$this->isCurrentLineEmpty() && $yaml[0] === $this->currentLine[-1]) {
break;
}
}
$value = '';
for ($i = 0, $linesCount = \count($lines), $previousLineWasNewline = false, $previousLineWasTerminatedWithBackslash = false; $i < $linesCount; ++$i) {
if ('' === trim($lines[$i])) {
$value .= "\n";
} elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
$value .= ' ';
}
if ('' !== trim($lines[$i]) && '\\' === substr($lines[$i], -1)) {
$value .= ltrim(substr($lines[$i], 0, -1));
} elseif ('' !== trim($lines[$i])) {
$value .= trim($lines[$i]);
}
if ('' === trim($lines[$i])) {
$previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false;
} elseif ('\\' === substr($lines[$i], -1)) {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = true;
} else {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = false;
}
}
return $value;
for ($i = 1; isset($yaml[$i]) && $quotation !== $yaml[$i]; ++$i) {
}
// quoted single line string
if (isset($yaml[$i]) && $quotation === $yaml[$i]) {
return $yaml;
}
$lines = [$yaml];
while ($this->moveToNextLine()) {
for ($i = 1; isset($this->currentLine[$i]) && $quotation !== $this->currentLine[$i]; ++$i) {
}
$lines[] = trim($this->currentLine);
if (isset($this->currentLine[$i]) && $quotation === $this->currentLine[$i]) {
break;
}
}
}
private function lexInlineMapping(string $yaml): string
{
if ('' === $yaml || '{' !== $yaml[0]) {
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));
}
for ($i = 1; isset($yaml[$i]) && '}' !== $yaml[$i]; ++$i) {
}
if (isset($yaml[$i]) && '}' === $yaml[$i]) {
return $yaml;
}
$lines = [$yaml];
while ($this->moveToNextLine()) {
$lines[] = $this->currentLine;
}
return implode("\n", $lines);
}
private function lexInlineSequence(string $yaml): string
{
if ('' === $yaml || '[' !== $yaml[0]) {
throw new \InvalidArgumentException(sprintf('"%s" is not a sequence.', $yaml));
}
for ($i = 1; isset($yaml[$i]) && ']' !== $yaml[$i]; ++$i) {
}
if (isset($yaml[$i]) && ']' === $yaml[$i]) {
return $yaml;
}
$value = $yaml;
while ($this->moveToNextLine()) {
for ($i = 1; isset($this->currentLine[$i]) && ']' !== $this->currentLine[$i]; ++$i) {
}
$value .= trim($this->currentLine);
if (isset($this->currentLine[$i]) && ']' === $this->currentLine[$i]) {
break;
}
}
return $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
/**
* Escaper encapsulates escaping rules for single and double-quoted
* YAML strings.
*
* @author Matthew Lewinski <matthew@lewinski.org>
*
* @internal
*/
class Escaper
{
// Characters that would cause a dumped string to require double quoting.
const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\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 static $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",
"\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9",
];
private static $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',
'\\N', '\\_', '\\L', '\\P',
];
/**
* Determines if a PHP value would require double quoting in YAML.
*
* @param string $value A PHP value
*
* @return bool True if the value would require double quotes
*/
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
*
* @return string The quoted, escaped string
*/
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
*
* @return bool True if the value would require single quotes
*/
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[ \- ? | < > = ! % @ ` ]/x', $value);
}
/**
* Escapes and surrounds a PHP value with single quotes.
*
* @param string $value A PHP value
*
* @return string The quoted, escaped string
*/
public static function escapeWithSingleQuotes(string $value): string
{
return sprintf("'%s'", str_replace('\'', '\'\'', $value));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
/**
* Unescaper encapsulates unescaping rules for single and double-quoted
* YAML strings.
*
* @author Matthew Lewinski <matthew@lewinski.org>
*
* @internal
*/
class Unescaper
{
/**
* Regex fragment that matches an escaped character in a double quoted string.
*/
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
*
* @return string The unescaped string
*/
public function unescapeSingleQuotedString(string $value): string
{
return str_replace('\'\'', '\'', $value);
}
/**
* Unescapes a double quoted string.
*
* @param string $value A double quoted string
*
* @return string The unescaped 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
*
* @return string The unescaped character
*/
private function unescapeCharacter(string $value): string
{
switch ($value[1]) {
case '0':
return "\x0";
case 'a':
return "\x7";
case 'b':
return "\x8";
case 't':
return "\t";
case "\t":
return "\t";
case 'n':
return "\n";
case 'v':
return "\xB";
case 'f':
return "\xC";
case 'r':
return "\r";
case 'e':
return "\x1B";
case ' ':
return ' ';
case '"':
return '"';
case '/':
return '/';
case '\\':
return '\\';
case 'N':
// U+0085 NEXT LINE
return "\xC2\x85";
case '_':
// U+00A0 NO-BREAK SPACE
return "\xC2\xA0";
case 'L':
// U+2028 LINE SEPARATOR
return "\xE2\x80\xA8";
case 'P':
// U+2029 PARAGRAPH SEPARATOR
return "\xE2\x80\xA9";
case 'x':
return self::utf8chr(hexdec(substr($value, 2, 2)));
case 'u':
return self::utf8chr(hexdec(substr($value, 2, 4)));
case 'U':
return 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);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Tag\TaggedValue;
/**
* Dumper dumps PHP variables to YAML strings.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class Dumper
{
/**
* The amount of spaces to use for indentation of nested nodes.
*
* @var int
*/
protected $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
*
* @return string The YAML representation of the PHP value
*/
public function dump($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 ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\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 = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : '';
$output .= sprintf("%s%s%s |%s\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator);
foreach (explode("\n", $value) as $row) {
$output .= sprintf("%s%s%s\n", $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 ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($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 = (' ' === substr($value->getValue(), 0, 1)) ? (string) $this->indentation : '';
$output .= sprintf(" |%s\n", $blockIndentationIndicator);
foreach (explode("\n", $value->getValue()) as $row) {
$output .= sprintf("%s%s%s\n", $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;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Exception\DumpException;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Tag\TaggedValue;
/**
* Inline implements a YAML parser/dumper for the YAML inline syntax.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
class Inline
{
const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
public static $parsedLineNumber = -1;
public static $parsedFilename;
private static $exceptionOnInvalidType = false;
private static $objectSupport = false;
private static $objectForMap = false;
private static $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 string $value A YAML string
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
* @param array $references Mapping of variable names to values
*
* @return mixed A PHP value
*
* @throws ParseException
*/
public static function parse(string $value = null, int $flags = 0, array $references = [])
{
self::initialize($flags);
$value = trim($value);
if ('' === $value) {
return '';
}
if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('ASCII');
}
try {
$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;
} finally {
if (isset($mbEncoding)) {
mb_internal_encoding($mbEncoding);
}
}
}
/**
* 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
*
* @return string The YAML string representing the PHP value
*
* @throws DumpException When trying to dump PHP resource
*/
public static function dump($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 \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 ctype_digit($value):
return \is_string($value) ? "'$value'" : (int) $value;
case is_numeric($value):
$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.
$repr = '!!float '.$repr;
}
} 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):
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.
*
* @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check
*
* @return bool true if value is hash array, false otherwise
*/
public static function isHash($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
*
* @return string The YAML string representing the PHP array
*/
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.
*
* @return mixed
*
* @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 = [])
{
if (\in_array($scalar[$i], ['"', "'"])) {
// quoted scalar
$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
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);
}
}
return $output;
}
/**
* Parses a YAML quoted scalar.
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseQuotedScalar(string $scalar, int &$i): 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, \strlen($match[0]) - 2);
$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:
$isQuoted = \in_array($sequence[$i], ['"', "'"]);
$value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references);
// the value can be an array if a reference has been resolved to an array var
if (\is_string($value) && !$isQuoted && false !== strpos($value, ': ')) {
// embedded mapping?
try {
$pos = 0;
$value = self::parseMapping('{'.$value.'}', $flags, $pos, $references);
} catch (\InvalidArgumentException $e) {
// no, it's not
}
}
--$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.
*
* @return array|\stdClass
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseMapping(string $mapping, int $flags, int &$i = 0, array $references = [])
{
$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);
// 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);
}
--$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.
*
* @return mixed The evaluated YAML string
*
* @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 = [])
{
$scalar = trim($scalar);
$scalarLower = strtolower($scalar);
if (0 === strpos($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];
}
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 0 === strpos($scalar, '!!str '):
return (string) substr($scalar, 6);
case 0 === strpos($scalar, '! '):
return substr($scalar, 2);
case 0 === strpos($scalar, '!php/object'):
if (self::$objectSupport) {
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 0 === strpos($scalar, '!php/const'):
if (self::$constantSupport) {
$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. Have you forgotten to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
return null;
case 0 === strpos($scalar, '!!float '):
return (float) substr($scalar, 8);
case 0 === strpos($scalar, '!!binary '):
return self::evaluateBinaryScalar(substr($scalar, 9));
default:
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);
}
// Optimize for returning strings.
// no break
case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || is_numeric($scalar[0]):
if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) {
$scalar = str_replace('_', '', (string) $scalar);
}
switch (true) {
case ctype_digit($scalar):
$raw = $scalar;
$cast = (int) $scalar;
return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
$raw = $scalar;
$cast = (int) $scalar;
return '0' == $scalar[1] ? -octdec(substr($scalar, 1)) : (($raw === (string) $cast) ? $cast : $raw);
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):
if (Yaml::PARSE_DATETIME & $flags) {
// When no timezone is provided in the parsed date, YAML spec says we must assume UTC.
return new \DateTime($scalar, new \DateTimeZone('UTC'));
}
$timeZone = date_default_timezone_get();
date_default_timezone_set('UTC');
$time = strtotime($scalar);
date_default_timezone_set($timeZone);
return $time;
}
}
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(sprintf('Using the unquoted scalar value "!" is not supported. You must quote it.', $value), 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)
{
return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value);
}
/**
* Gets a regex that matches a YAML date.
*
* @return string The regular expression
*
* @see http://www.yaml.org/spec/1.2/spec.html#id2761573
*/
private static function getTimestampRegex(): string
{
return <<<EOF
~^
(?P<year>[0-9][0-9][0-9][0-9])
-(?P<month>[0-9][0-9]?)
-(?P<day>[0-9][0-9]?)
(?:(?:[Tt]|[ \t]+)
(?P<hour>[0-9][0-9]?)
:(?P<minute>[0-9][0-9])
:(?P<second>[0-9][0-9])
(?:\.(?P<fraction>[0-9]*))?
(?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
(?::(?P<tz_minute>[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';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
/**
* Yaml offers convenience methods to load and dump YAML.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class Yaml
{
const DUMP_OBJECT = 1;
const PARSE_EXCEPTION_ON_INVALID_TYPE = 2;
const PARSE_OBJECT = 4;
const PARSE_OBJECT_FOR_MAP = 8;
const DUMP_EXCEPTION_ON_INVALID_TYPE = 16;
const PARSE_DATETIME = 32;
const DUMP_OBJECT_AS_MAP = 64;
const DUMP_MULTI_LINE_LITERAL_BLOCK = 128;
const PARSE_CONSTANT = 256;
const PARSE_CUSTOM_TAGS = 512;
const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024;
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
*
* @return mixed The YAML converted to a PHP value
*
* @throws ParseException If the file could not be read or the YAML is not valid
*/
public static function parseFile(string $filename, int $flags = 0)
{
$yaml = new Parser();
return $yaml->parseFile($filename, $flags);
}
/**
* Parses YAML into a PHP value.
*
* Usage:
* <code>
* $array = Yaml::parse(file_get_contents('config.yml'));
* print_r($array);
* </code>
*
* @param string $input A string containing YAML
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed The YAML converted to a PHP value
*
* @throws ParseException If the YAML is not valid
*/
public static function parse(string $input, int $flags = 0)
{
$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
*
* @return string A YAML string representing the original PHP value
*/
public static function dump($input, int $inline = 2, int $indent = 4, int $flags = 0): string
{
$yaml = new Dumper($indent);
return $yaml->dump($input, $inline, 0, $flags);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Command;
use Symfony\Component\Console\Command\Command;
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 Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Yaml;
/**
* Validates YAML files syntax and outputs encountered errors.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class LintCommand extends Command
{
protected static $defaultName = 'lint:yaml';
private $parser;
private $format;
private $displayCorrectFiles;
private $directoryIteratorProvider;
private $isReadableProvider;
public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null)
{
parent::__construct($name);
$this->directoryIteratorProvider = $directoryIteratorProvider;
$this->isReadableProvider = $isReadableProvider;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDescription('Lints a file and outputs encountered errors')
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
->addOption('parse-tags', null, InputOption::VALUE_NONE, 'Parse custom tags')
->setHelp(<<<EOF
The <info>%command.name%</info> command lints a YAML file and outputs to STDOUT
the first encountered syntax error.
You can validates YAML contents passed from STDIN:
<info>cat filename | php %command.full_name% -</info>
You can also validate the syntax of a file:
<info>php %command.full_name% filename</info>
Or of a whole directory:
<info>php %command.full_name% dirname</info>
<info>php %command.full_name% dirname --format=json</info>
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$filenames = (array) $input->getArgument('filename');
$this->format = $input->getOption('format');
$this->displayCorrectFiles = $output->isVerbose();
$flags = $input->getOption('parse-tags') ? Yaml::PARSE_CUSTOM_TAGS : 0;
if (['-'] === $filenames) {
return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]);
}
// @deprecated to be removed in 5.0
if (!$filenames) {
if (0 === ftell(STDIN)) {
@trigger_error('Piping content from STDIN to the "lint:yaml" command without passing the dash symbol "-" as argument is deprecated since Symfony 4.4.', E_USER_DEPRECATED);
return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]);
}
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) {
$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
{
switch ($this->format) {
case 'txt':
return $this->displayTxt($io, $files);
case 'json':
return $this->displayJson($io, $files);
default:
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format));
}
}
private function displayTxt(SymfonyStyle $io, array $filesInfo): int
{
$countFiles = \count($filesInfo);
$erroredFiles = 0;
$suggestTagOption = false;
foreach ($filesInfo as $info) {
if ($info['valid'] && $this->displayCorrectFiles) {
$io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
} elseif (!$info['valid']) {
++$erroredFiles;
$io->text('<error> ERROR </error>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
$io->text(sprintf('<error> >> %s</error>', $info['message']));
if (false !== strpos($info['message'], 'PARSE_CUSTOM_TAGS')) {
$suggestTagOption = true;
}
}
}
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']) && false !== strpos($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
{
if (!$this->parser) {
$this->parser = new Parser();
}
return $this->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);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Exception;
/**
* Exception class thrown when an error occurs during dumping.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DumpException extends RuntimeException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Exception;
/**
* Exception interface for all exceptions thrown by the component.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Exception;
/**
* Exception class thrown when an error occurs during parsing.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Exception;
/**
* Exception class thrown when an error occurs during parsing.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ParseException extends RuntimeException
{
private $parsedFile;
private $parsedLine;
private $snippet;
private $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
* @param \Exception|null $previous The previous exception
*/
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.
*
* @return string The snippet of code
*/
public function getSnippet()
{
return $this->snippet;
}
/**
* Sets the snippet of code near the error.
*
* @param string $snippet The code snippet
*/
public function setSnippet($snippet)
{
$this->snippet = $snippet;
$this->updateRepr();
}
/**
* Gets the filename where the error occurred.
*
* This method returns null if a string is parsed.
*
* @return string The filename
*/
public function getParsedFile()
{
return $this->parsedFile;
}
/**
* Sets the filename where the error occurred.
*
* @param string $parsedFile The filename
*/
public function setParsedFile($parsedFile)
{
$this->parsedFile = $parsedFile;
$this->updateRepr();
}
/**
* Gets the line where the error occurred.
*
* @return int The file line
*/
public function getParsedLine()
{
return $this->parsedLine;
}
/**
* Sets the line where the error occurred.
*
* @param int $parsedLine The file line
*/
public function setParsedLine($parsedLine)
{
$this->parsedLine = $parsedLine;
$this->updateRepr();
}
private function updateRepr()
{
$this->message = $this->rawMessage;
$dot = false;
if ('.' === substr($this->message, -1)) {
$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 .= '.';
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Tag;
/**
* @author Nicolas Grekas <p@tchwork.com>
* @author Guilhem N. <egetick@gmail.com>
*/
final class TaggedValue
{
private $tag;
private $value;
public function __construct(string $tag, $value)
{
$this->tag = $tag;
$this->value = $value;
}
public function getTag(): string
{
return $this->tag;
}
public function getValue()
{
return $this->value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
/**
* A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method.
*
* The getSubscribedServices method returns an array of service types required by such instances,
* optionally keyed by the service names used internally. Service types that start with an interrogation
* mark "?" are optional, while the other ones are mandatory service dependencies.
*
* The injected service locators SHOULD NOT allow access to any other services not specified by the method.
*
* It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally.
* This interface does not dictate any injection method for these service locators, although constructor
* injection is recommended.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface ServiceSubscriberInterface
{
/**
* Returns an array of service types required by such instances, optionally keyed by the service names used internally.
*
* For mandatory dependencies:
*
* * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name
* internally to fetch a service which must implement Psr\Log\LoggerInterface.
* * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name
* internally to fetch an iterable of Psr\Log\LoggerInterface instances.
* * ['Psr\Log\LoggerInterface'] is a shortcut for
* * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface']
*
* otherwise:
*
* * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency
* * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency
* * ['?Psr\Log\LoggerInterface'] is a shortcut for
* * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface']
*
* @return array The required service types, optionally keyed by service names
*/
public static function getSubscribedServices();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
/**
* Provides a way to reset an object to its initial state.
*
* When calling the "reset()" method on an object, it should be put back to its
* initial state. This usually means clearing any internal buffers and forwarding
* the call to internal dependencies. All properties of the object should be put
* back to the same state it had when it was first ready to use.
*
* This method could be called, for example, to recycle objects that are used as
* services, so that they can be used to handle several requests in the same
* process loop (note that we advise making your services stateless instead of
* implementing this interface when possible.)
*/
interface ResetInterface
{
public function reset();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerInterface;
/**
* A ServiceProviderInterface exposes the identifiers and the types of services provided by a container.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Mateusz Sip <mateusz.sip@gmail.com>
*/
interface ServiceProviderInterface extends ContainerInterface
{
/**
* Returns an associative array of service types keyed by the identifiers provided by the current container.
*
* Examples:
*
* * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface
* * ['foo' => '?'] means the container provides service name "foo" of unspecified type
* * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null
*
* @return string[] The provided service types, keyed by service names
*/
public function getProvidedServices(): array;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerInterface;
/**
* Implementation of ServiceSubscriberInterface that determines subscribed services from
* private method return types. Service ids are available as "ClassName::methodName".
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
trait ServiceSubscriberTrait
{
/** @var ContainerInterface */
protected $container;
public static function getSubscribedServices(): array
{
static $services;
if (null !== $services) {
return $services;
}
$services = \is_callable(['parent', __FUNCTION__]) ? parent::getSubscribedServices() : [];
foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) {
continue;
}
if (self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !$returnType->isBuiltin()) {
$services[self::class.'::'.$method->name] = '?'.$returnType->getName();
}
}
return $services;
}
/**
* @required
*/
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
if (\is_callable(['parent', __FUNCTION__])) {
return parent::setContainer($container);
}
return null;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
/**
* A trait to help implement ServiceProviderInterface.
*
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
trait ServiceLocatorTrait
{
private $factories;
private $loading = [];
private $providedTypes;
/**
* @param callable[] $factories
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function has($id)
{
return isset($this->factories[$id]);
}
/**
* {@inheritdoc}
*/
public function get($id)
{
if (!isset($this->factories[$id])) {
throw $this->createNotFoundException($id);
}
if (isset($this->loading[$id])) {
$ids = array_values($this->loading);
$ids = \array_slice($this->loading, array_search($id, $ids));
$ids[] = $id;
throw $this->createCircularReferenceException($id, $ids);
}
$this->loading[$id] = $id;
try {
return $this->factories[$id]($this);
} finally {
unset($this->loading[$id]);
}
}
/**
* {@inheritdoc}
*/
public function getProvidedServices(): array
{
if (null === $this->providedTypes) {
$this->providedTypes = [];
foreach ($this->factories as $name => $factory) {
if (!\is_callable($factory)) {
$this->providedTypes[$name] = '?';
} else {
$type = (new \ReflectionFunction($factory))->getReturnType();
$this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').$type->getName() : '?';
}
}
}
return $this->providedTypes;
}
private function createNotFoundException(string $id): NotFoundExceptionInterface
{
if (!$alternatives = array_keys($this->factories)) {
$message = 'is empty...';
} else {
$last = array_pop($alternatives);
if ($alternatives) {
$message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last);
} else {
$message = sprintf('only knows about the "%s" service.', $last);
}
}
if ($this->loading) {
$message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message);
} else {
$message = sprintf('Service "%s" not found: the current service locator %s', $id, $message);
}
return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface {
};
}
private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface
{
return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface {
};
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
/**
* Generic executable finder.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ExecutableFinder
{
private $suffixes = ['.exe', '.bat', '.cmd', '.com'];
/**
* Replaces default suffixes of executable.
*/
public function setSuffixes(array $suffixes)
{
$this->suffixes = $suffixes;
}
/**
* Adds new possible suffix to check for executable.
*
* @param string $suffix
*/
public function addSuffix($suffix)
{
$this->suffixes[] = $suffix;
}
/**
* Finds an executable by name.
*
* @param string $name The executable name (without the extension)
* @param string|null $default The default to return if no executable is found
* @param array $extraDirs Additional dirs to check into
*
* @return string|null The executable path or default value
*/
public function find($name, $default = null, array $extraDirs = [])
{
if (ini_get('open_basedir')) {
$searchPath = array_merge(explode(PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
$dirs = [];
foreach ($searchPath as $path) {
// Silencing against https://bugs.php.net/69240
if (@is_dir($path)) {
$dirs[] = $path;
} else {
if (basename($path) == $name && @is_executable($path)) {
return $path;
}
}
}
} else {
$dirs = array_merge(
explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
$extraDirs
);
}
$suffixes = [''];
if ('\\' === \DIRECTORY_SEPARATOR) {
$pathExt = getenv('PATHEXT');
$suffixes = array_merge($pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
}
foreach ($suffixes as $suffix) {
foreach ($dirs as $dir) {
if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
return $file;
}
}
}
return $default;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
use Symfony\Component\Process\Process;
/**
* UnixPipes implementation uses unix pipes as handles.
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
class UnixPipes extends AbstractPipes
{
private $ttyMode;
private $ptyMode;
private $haveReadSupport;
public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport)
{
$this->ttyMode = $ttyMode;
$this->ptyMode = $ptyMode;
$this->haveReadSupport = $haveReadSupport;
parent::__construct($input);
}
public function __destruct()
{
$this->close();
}
/**
* {@inheritdoc}
*/
public function getDescriptors(): array
{
if (!$this->haveReadSupport) {
$nullstream = fopen('/dev/null', 'c');
return [
['pipe', 'r'],
$nullstream,
$nullstream,
];
}
if ($this->ttyMode) {
return [
['file', '/dev/tty', 'r'],
['file', '/dev/tty', 'w'],
['file', '/dev/tty', 'w'],
];
}
if ($this->ptyMode && Process::isPtySupported()) {
return [
['pty'],
['pty'],
['pty'],
];
}
return [
['pipe', 'r'],
['pipe', 'w'], // stdout
['pipe', 'w'], // stderr
];
}
/**
* {@inheritdoc}
*/
public function getFiles(): array
{
return [];
}
/**
* {@inheritdoc}
*/
public function readAndWrite(bool $blocking, bool $close = false): array
{
$this->unblock();
$w = $this->write();
$read = $e = [];
$r = $this->pipes;
unset($r[0]);
// let's have a look if something changed in streams
set_error_handler([$this, 'handleError']);
if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
restore_error_handler();
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occurred, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
$this->pipes = [];
}
return $read;
}
restore_error_handler();
foreach ($r as $pipe) {
// prior PHP 5.4 the array passed to stream_select is modified and
// lose key association, we have to find back the key
$read[$type = array_search($pipe, $this->pipes, true)] = '';
do {
$data = fread($pipe, self::CHUNK_SIZE);
$read[$type] .= $data;
} while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));
if (!isset($read[$type][0])) {
unset($read[$type]);
}
if ($close && feof($pipe)) {
fclose($pipe);
unset($this->pipes[$type]);
}
}
return $read;
}
/**
* {@inheritdoc}
*/
public function haveReadSupport(): bool
{
return $this->haveReadSupport;
}
/**
* {@inheritdoc}
*/
public function areOpen(): bool
{
return (bool) $this->pipes;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Process;
/**
* WindowsPipes implementation uses temporary files as handles.
*
* @see https://bugs.php.net/51800
* @see https://bugs.php.net/65650
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
class WindowsPipes extends AbstractPipes
{
private $files = [];
private $fileHandles = [];
private $lockHandles = [];
private $readBytes = [
Process::STDOUT => 0,
Process::STDERR => 0,
];
private $haveReadSupport;
public function __construct($input, bool $haveReadSupport)
{
$this->haveReadSupport = $haveReadSupport;
if ($this->haveReadSupport) {
// Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
// Workaround for this problem is to use temporary files instead of pipes on Windows platform.
//
// @see https://bugs.php.net/51800
$pipes = [
Process::STDOUT => Process::OUT,
Process::STDERR => Process::ERR,
];
$tmpDir = sys_get_temp_dir();
$lastError = 'unknown reason';
set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
for ($i = 0;; ++$i) {
foreach ($pipes as $pipe => $name) {
$file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
if (!$h = fopen($file.'.lock', 'w')) {
restore_error_handler();
throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $lastError));
}
if (!flock($h, LOCK_EX | LOCK_NB)) {
continue 2;
}
if (isset($this->lockHandles[$pipe])) {
flock($this->lockHandles[$pipe], LOCK_UN);
fclose($this->lockHandles[$pipe]);
}
$this->lockHandles[$pipe] = $h;
if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) {
flock($this->lockHandles[$pipe], LOCK_UN);
fclose($this->lockHandles[$pipe]);
unset($this->lockHandles[$pipe]);
continue 2;
}
$this->fileHandles[$pipe] = $h;
$this->files[$pipe] = $file;
}
break;
}
restore_error_handler();
}
parent::__construct($input);
}
public function __destruct()
{
$this->close();
}
/**
* {@inheritdoc}
*/
public function getDescriptors(): array
{
if (!$this->haveReadSupport) {
$nullstream = fopen('NUL', 'c');
return [
['pipe', 'r'],
$nullstream,
$nullstream,
];
}
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
// We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
// So we redirect output within the commandline and pass the nul device to the process
return [
['pipe', 'r'],
['file', 'NUL', 'w'],
['file', 'NUL', 'w'],
];
}
/**
* {@inheritdoc}
*/
public function getFiles(): array
{
return $this->files;
}
/**
* {@inheritdoc}
*/
public function readAndWrite(bool $blocking, bool $close = false): array
{
$this->unblock();
$w = $this->write();
$read = $r = $e = [];
if ($blocking) {
if ($w) {
@stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
} elseif ($this->fileHandles) {
usleep(Process::TIMEOUT_PRECISION * 1E6);
}
}
foreach ($this->fileHandles as $type => $fileHandle) {
$data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
if (isset($data[0])) {
$this->readBytes[$type] += \strlen($data);
$read[$type] = $data;
}
if ($close) {
ftruncate($fileHandle, 0);
fclose($fileHandle);
flock($this->lockHandles[$type], LOCK_UN);
fclose($this->lockHandles[$type]);
unset($this->fileHandles[$type], $this->lockHandles[$type]);
}
}
return $read;
}
/**
* {@inheritdoc}
*/
public function haveReadSupport(): bool
{
return $this->haveReadSupport;
}
/**
* {@inheritdoc}
*/
public function areOpen(): bool
{
return $this->pipes && $this->fileHandles;
}
/**
* {@inheritdoc}
*/
public function close()
{
parent::close();
foreach ($this->fileHandles as $type => $handle) {
ftruncate($handle, 0);
fclose($handle);
flock($this->lockHandles[$type], LOCK_UN);
fclose($this->lockHandles[$type]);
}
$this->fileHandles = $this->lockHandles = [];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
use Symfony\Component\Process\Exception\InvalidArgumentException;
/**
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
abstract class AbstractPipes implements PipesInterface
{
public $pipes = [];
private $inputBuffer = '';
private $input;
private $blocked = true;
private $lastError;
/**
* @param resource|string|int|float|bool|\Iterator|null $input
*/
public function __construct($input)
{
if (\is_resource($input) || $input instanceof \Iterator) {
$this->input = $input;
} elseif (\is_string($input)) {
$this->inputBuffer = $input;
} else {
$this->inputBuffer = (string) $input;
}
}
/**
* {@inheritdoc}
*/
public function close()
{
foreach ($this->pipes as $pipe) {
fclose($pipe);
}
$this->pipes = [];
}
/**
* Returns true if a system call has been interrupted.
*/
protected function hasSystemCallBeenInterrupted(): bool
{
$lastError = $this->lastError;
$this->lastError = null;
// stream_select returns false when the `select` system call is interrupted by an incoming signal
return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
}
/**
* Unblocks streams.
*/
protected function unblock()
{
if (!$this->blocked) {
return;
}
foreach ($this->pipes as $pipe) {
stream_set_blocking($pipe, 0);
}
if (\is_resource($this->input)) {
stream_set_blocking($this->input, 0);
}
$this->blocked = false;
}
/**
* Writes input to stdin.
*
* @throws InvalidArgumentException When an input iterator yields a non supported value
*/
protected function write(): ?array
{
if (!isset($this->pipes[0])) {
return null;
}
$input = $this->input;
if ($input instanceof \Iterator) {
if (!$input->valid()) {
$input = null;
} elseif (\is_resource($input = $input->current())) {
stream_set_blocking($input, 0);
} elseif (!isset($this->inputBuffer[0])) {
if (!\is_string($input)) {
if (!is_scalar($input)) {
throw new InvalidArgumentException(sprintf('%s yielded a value of type "%s", but only scalars and stream resources are supported', \get_class($this->input), \gettype($input)));
}
$input = (string) $input;
}
$this->inputBuffer = $input;
$this->input->next();
$input = null;
} else {
$input = null;
}
}
$r = $e = [];
$w = [$this->pipes[0]];
// let's have a look if something changed in streams
if (false === @stream_select($r, $w, $e, 0, 0)) {
return null;
}
foreach ($w as $stdin) {
if (isset($this->inputBuffer[0])) {
$written = fwrite($stdin, $this->inputBuffer);
$this->inputBuffer = substr($this->inputBuffer, $written);
if (isset($this->inputBuffer[0])) {
return [$this->pipes[0]];
}
}
if ($input) {
for (;;) {
$data = fread($input, self::CHUNK_SIZE);
if (!isset($data[0])) {
break;
}
$written = fwrite($stdin, $data);
$data = substr($data, $written);
if (isset($data[0])) {
$this->inputBuffer = $data;
return [$this->pipes[0]];
}
}
if (feof($input)) {
if ($this->input instanceof \Iterator) {
$this->input->next();
} else {
$this->input = null;
}
}
}
}
// no input to read on resource, buffer is empty
if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
$this->input = null;
fclose($this->pipes[0]);
unset($this->pipes[0]);
} elseif (!$w) {
return [$this->pipes[0]];
}
return null;
}
/**
* @internal
*/
public function handleError($type, $msg)
{
$this->lastError = $msg;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
/**
* PipesInterface manages descriptors and pipes for the use of proc_open.
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
interface PipesInterface
{
const CHUNK_SIZE = 16384;
/**
* Returns an array of descriptors for the use of proc_open.
*/
public function getDescriptors(): array;
/**
* Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
*
* @return string[]
*/
public function getFiles(): array;
/**
* Reads data in file handles and pipes.
*
* @param bool $blocking Whether to use blocking calls or not
* @param bool $close Whether to close pipes if they've reached EOF
*
* @return string[] An array of read data indexed by their fd
*/
public function readAndWrite(bool $blocking, bool $close = false): array;
/**
* Returns if the current state has open file handles or pipes.
*/
public function areOpen(): bool;
/**
* Returns if pipes are able to read output.
*/
public function haveReadSupport(): bool;
/**
* Closes file handles and pipes.
*/
public function close();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* Provides a way to continuously write to the input of a Process until the InputStream is closed.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class InputStream implements \IteratorAggregate
{
/** @var callable|null */
private $onEmpty = null;
private $input = [];
private $open = true;
/**
* Sets a callback that is called when the write buffer becomes empty.
*/
public function onEmpty(callable $onEmpty = null)
{
$this->onEmpty = $onEmpty;
}
/**
* Appends an input to the write buffer.
*
* @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
* stream resource or \Traversable
*/
public function write($input)
{
if (null === $input) {
return;
}
if ($this->isClosed()) {
throw new RuntimeException(sprintf('%s is closed', static::class));
}
$this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
}
/**
* Closes the write buffer.
*/
public function close()
{
$this->open = false;
}
/**
* Tells whether the write buffer is closed or not.
*/
public function isClosed()
{
return !$this->open;
}
/**
* @return \Traversable
*/
public function getIterator()
{
$this->open = true;
while ($this->open || $this->input) {
if (!$this->input) {
yield '';
continue;
}
$current = array_shift($this->input);
if ($current instanceof \Iterator) {
yield from $current;
} else {
yield $current;
}
if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
$this->write($onEmpty($this));
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* PhpProcess runs a PHP script in an independent process.
*
* $p = new PhpProcess('<?php echo "foo"; ?>');
* $p->run();
* print $p->getOutput()."\n";
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class PhpProcess extends Process
{
/**
* @param string $script The PHP script to run (as a string)
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param int $timeout The timeout in seconds
* @param array|null $php Path to the PHP binary to use with any additional arguments
*/
public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null)
{
if (null === $php) {
$executableFinder = new PhpExecutableFinder();
$php = $executableFinder->find(false);
$php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
}
if ('phpdbg' === \PHP_SAPI) {
$file = tempnam(sys_get_temp_dir(), 'dbg');
file_put_contents($file, $script);
register_shutdown_function('unlink', $file);
$php[] = $file;
$script = null;
}
parent::__construct($php, $cwd, $env, $script, $timeout);
}
/**
* Sets the path to the PHP binary to use.
*
* @deprecated since Symfony 4.2, use the $php argument of the constructor instead.
*/
public function setPhpBinary($php)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2, use the $php argument of the constructor instead.', __METHOD__), E_USER_DEPRECATED);
$this->setCommandLine($php);
}
/**
* {@inheritdoc}
*/
public function start(callable $callback = null, array $env = [])
{
if (null === $this->getCommandLine()) {
throw new RuntimeException('Unable to find the PHP executable.');
}
parent::start($callback, $env);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
/**
* An executable finder specifically designed for the PHP executable.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PhpExecutableFinder
{
private $executableFinder;
public function __construct()
{
$this->executableFinder = new ExecutableFinder();
}
/**
* Finds The PHP executable.
*
* @param bool $includeArgs Whether or not include command arguments
*
* @return string|false The PHP executable path or false if it cannot be found
*/
public function find($includeArgs = true)
{
if ($php = getenv('PHP_BINARY')) {
if (!is_executable($php)) {
$command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v';
if ($php = strtok(exec($command.' '.escapeshellarg($php)), PHP_EOL)) {
if (!is_executable($php)) {
return false;
}
} else {
return false;
}
}
return $php;
}
$args = $this->findArguments();
$args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
// PHP_BINARY return the current sapi executable
if (PHP_BINARY && \in_array(\PHP_SAPI, ['cgi-fcgi', 'cli', 'cli-server', 'phpdbg'], true)) {
return PHP_BINARY.$args;
}
if ($php = getenv('PHP_PATH')) {
if (!@is_executable($php)) {
return false;
}
return $php;
}
if ($php = getenv('PHP_PEAR_PHP_BIN')) {
if (@is_executable($php)) {
return $php;
}
}
if (@is_executable($php = PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
return $php;
}
$dirs = [PHP_BINDIR];
if ('\\' === \DIRECTORY_SEPARATOR) {
$dirs[] = 'C:\xampp\php\\';
}
return $this->executableFinder->find('php', false, $dirs);
}
/**
* Finds the PHP executable arguments.
*
* @return array The PHP executable arguments
*/
public function findArguments()
{
$arguments = [];
if ('phpdbg' === \PHP_SAPI) {
$arguments[] = '-qrr';
}
return $arguments;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception that is thrown when a process has been signaled.
*
* @author Sullivan Senechal <soullivaneuh@gmail.com>
*/
final class ProcessSignaledException extends RuntimeException
{
private $process;
public function __construct(Process $process)
{
$this->process = $process;
parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
}
public function getProcess(): Process
{
return $this->process;
}
public function getSignal(): int
{
return $this->getProcess()->getTermSignal();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* LogicException for the Process Component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* Marker Interface for the Process Component.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception for failed processes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ProcessFailedException extends RuntimeException
{
private $process;
public function __construct(Process $process)
{
if ($process->isSuccessful()) {
throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
}
$error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
$process->getCommandLine(),
$process->getExitCode(),
$process->getExitCodeText(),
$process->getWorkingDirectory()
);
if (!$process->isOutputDisabled()) {
$error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
$process->getOutput(),
$process->getErrorOutput()
);
}
parent::__construct($error);
$this->process = $process;
}
public function getProcess()
{
return $this->process;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* RuntimeException for the Process Component.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception that is thrown when a process times out.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ProcessTimedOutException extends RuntimeException
{
const TYPE_GENERAL = 1;
const TYPE_IDLE = 2;
private $process;
private $timeoutType;
public function __construct(Process $process, int $timeoutType)
{
$this->process = $process;
$this->timeoutType = $timeoutType;
parent::__construct(sprintf(
'The process "%s" exceeded the timeout of %s seconds.',
$process->getCommandLine(),
$this->getExceededTimeout()
));
}
public function getProcess()
{
return $this->process;
}
public function isGeneralTimeout()
{
return self::TYPE_GENERAL === $this->timeoutType;
}
public function isIdleTimeout()
{
return self::TYPE_IDLE === $this->timeoutType;
}
public function getExceededTimeout()
{
switch ($this->timeoutType) {
case self::TYPE_GENERAL:
return $this->process->getTimeout();
case self::TYPE_IDLE:
return $this->process->getIdleTimeout();
default:
throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* InvalidArgumentException for the Process Component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\InvalidArgumentException;
/**
* ProcessUtils is a bunch of utility methods.
*
* This class contains static methods only and is not meant to be instantiated.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class ProcessUtils
{
/**
* This class should not be instantiated.
*/
private function __construct()
{
}
/**
* Validates and normalizes a Process input.
*
* @param string $caller The name of method call that validates the input
* @param mixed $input The input to validate
*
* @return mixed The validated input
*
* @throws InvalidArgumentException In case the input is not valid
*/
public static function validateInput($caller, $input)
{
if (null !== $input) {
if (\is_resource($input)) {
return $input;
}
if (\is_string($input)) {
return $input;
}
if (is_scalar($input)) {
return (string) $input;
}
if ($input instanceof Process) {
return $input->getIterator($input::ITER_SKIP_ERR);
}
if ($input instanceof \Iterator) {
return $input;
}
if ($input instanceof \Traversable) {
return new \IteratorIterator($input);
}
throw new InvalidArgumentException(sprintf('%s only accepts strings, Traversable objects or stream resources.', $caller));
}
return $input;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Pipes\PipesInterface;
use Symfony\Component\Process\Pipes\UnixPipes;
use Symfony\Component\Process\Pipes\WindowsPipes;
/**
* Process is a thin wrapper around proc_* functions to easily
* start independent PHP processes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Romain Neutron <imprec@gmail.com>
*/
class Process implements \IteratorAggregate
{
const ERR = 'err';
const OUT = 'out';
const STATUS_READY = 'ready';
const STATUS_STARTED = 'started';
const STATUS_TERMINATED = 'terminated';
const STDIN = 0;
const STDOUT = 1;
const STDERR = 2;
// Timeout Precision in seconds.
const TIMEOUT_PRECISION = 0.2;
const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
private $callback;
private $hasCallback = false;
private $commandline;
private $cwd;
private $env;
private $input;
private $starttime;
private $lastOutputTime;
private $timeout;
private $idleTimeout;
private $exitcode;
private $fallbackStatus = [];
private $processInformation;
private $outputDisabled = false;
private $stdout;
private $stderr;
private $process;
private $status = self::STATUS_READY;
private $incrementalOutputOffset = 0;
private $incrementalErrorOutputOffset = 0;
private $tty = false;
private $pty;
private $useFileHandles = false;
/** @var PipesInterface */
private $processPipes;
private $latestSignal;
private static $sigchild;
/**
* Exit codes translation table.
*
* User-defined errors must use exit codes in the 64-113 range.
*/
public static $exitCodes = [
0 => 'OK',
1 => 'General error',
2 => 'Misuse of shell builtins',
126 => 'Invoked command cannot execute',
127 => 'Command not found',
128 => 'Invalid exit argument',
// signals
129 => 'Hangup',
130 => 'Interrupt',
131 => 'Quit and dump core',
132 => 'Illegal instruction',
133 => 'Trace/breakpoint trap',
134 => 'Process aborted',
135 => 'Bus error: "access to undefined portion of memory object"',
136 => 'Floating point exception: "erroneous arithmetic operation"',
137 => 'Kill (terminate immediately)',
138 => 'User-defined 1',
139 => 'Segmentation violation',
140 => 'User-defined 2',
141 => 'Write to pipe with no one reading',
142 => 'Signal raised by alarm',
143 => 'Termination (request to terminate)',
// 144 - not defined
145 => 'Child process terminated, stopped (or continued*)',
146 => 'Continue if stopped',
147 => 'Stop executing temporarily',
148 => 'Terminal stop signal',
149 => 'Background process attempting to read from tty ("in")',
150 => 'Background process attempting to write to tty ("out")',
151 => 'Urgent data available on socket',
152 => 'CPU time limit exceeded',
153 => 'File size limit exceeded',
154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
155 => 'Profiling timer expired',
// 156 - not defined
157 => 'Pollable event',
// 158 - not defined
159 => 'Bad syscall',
];
/**
* @param array $command The command to run and its arguments listed as separate entries
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
* @param int|float|null $timeout The timeout in seconds or null to disable
*
* @throws LogicException When proc_open is not installed
*/
public function __construct($command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
{
if (!\function_exists('proc_open')) {
throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
}
if (!\is_array($command)) {
@trigger_error(sprintf('Passing a command as string when creating a "%s" instance is deprecated since Symfony 4.2, pass it as an array of its arguments instead, or use the "Process::fromShellCommandline()" constructor if you need features provided by the shell.', __CLASS__), E_USER_DEPRECATED);
}
$this->commandline = $command;
$this->cwd = $cwd;
// on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
// on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
// @see : https://bugs.php.net/51800
// @see : https://bugs.php.net/50524
if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
$this->cwd = getcwd();
}
if (null !== $env) {
$this->setEnv($env);
}
$this->setInput($input);
$this->setTimeout($timeout);
$this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
$this->pty = false;
}
/**
* Creates a Process instance as a command-line to be run in a shell wrapper.
*
* Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
* This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
* shell wrapper and not to your commands.
*
* In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
* This will save escaping values, which is not portable nor secure anyway:
*
* $process = Process::fromShellCommandline('my_command "$MY_VAR"');
* $process->run(null, ['MY_VAR' => $theValue]);
*
* @param string $command The command line to pass to the shell of the OS
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
* @param int|float|null $timeout The timeout in seconds or null to disable
*
* @return static
*
* @throws LogicException When proc_open is not installed
*/
public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
{
$process = new static([], $cwd, $env, $input, $timeout);
$process->commandline = $command;
return $process;
}
public function __destruct()
{
$this->stop(0);
}
public function __clone()
{
$this->resetProcessData();
}
/**
* Runs the process.
*
* The callback receives the type of output (out or err) and
* some bytes from the output in real-time. It allows to have feedback
* from the independent process during execution.
*
* The STDOUT and STDERR are also available after the process is finished
* via the getOutput() and getErrorOutput() methods.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return int The exit status code
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @throws ProcessTimedOutException When process timed out
* @throws ProcessSignaledException When process stopped after receiving signal
* @throws LogicException In case a callback is provided and output has been disabled
*
* @final
*/
public function run(callable $callback = null, array $env = []): int
{
$this->start($callback, $env);
return $this->wait();
}
/**
* Runs the process.
*
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
*
* @return $this
*
* @throws ProcessFailedException if the process didn't terminate successfully
*
* @final
*/
public function mustRun(callable $callback = null, array $env = []): self
{
if (0 !== $this->run($callback, $env)) {
throw new ProcessFailedException($this);
}
return $this;
}
/**
* Starts the process and returns after writing the input to STDIN.
*
* This method blocks until all STDIN data is sent to the process then it
* returns while the process runs in the background.
*
* The termination of the process can be awaited with wait().
*
* The callback receives the type of output (out or err) and some bytes from
* the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @throws LogicException In case a callback is provided and output has been disabled
*/
public function start(callable $callback = null, array $env = [])
{
if ($this->isRunning()) {
throw new RuntimeException('Process is already running');
}
$this->resetProcessData();
$this->starttime = $this->lastOutputTime = microtime(true);
$this->callback = $this->buildCallback($callback);
$this->hasCallback = null !== $callback;
$descriptors = $this->getDescriptors();
if ($this->env) {
$env += $this->env;
}
$env += $this->getDefaultEnv();
if (\is_array($commandline = $this->commandline)) {
$commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
if ('\\' !== \DIRECTORY_SEPARATOR) {
// exec is mandatory to deal with sending a signal to the process
$commandline = 'exec '.$commandline;
}
} else {
$commandline = $this->replacePlaceholders($commandline, $env);
}
$options = ['suppress_errors' => true];
if ('\\' === \DIRECTORY_SEPARATOR) {
$options['bypass_shell'] = true;
$commandline = $this->prepareWindowsCommandLine($commandline, $env);
} elseif (!$this->useFileHandles && $this->isSigchildEnabled()) {
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
$descriptors[3] = ['pipe', 'w'];
// See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
$commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
$commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
// Workaround for the bug, when PTS functionality is enabled.
// @see : https://bugs.php.net/69442
$ptsWorkaround = fopen(__FILE__, 'r');
}
$envPairs = [];
foreach ($env as $k => $v) {
if (false !== $v) {
$envPairs[] = $k.'='.$v;
}
}
if (!is_dir($this->cwd)) {
throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
}
$this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $options);
if (!\is_resource($this->process)) {
throw new RuntimeException('Unable to launch a new process.');
}
$this->status = self::STATUS_STARTED;
if (isset($descriptors[3])) {
$this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
}
if ($this->tty) {
return;
}
$this->updateStatus(false);
$this->checkTimeout();
}
/**
* Restarts the process.
*
* Be warned that the process is cloned before being started.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return static
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
*
* @see start()
*
* @final
*/
public function restart(callable $callback = null, array $env = []): self
{
if ($this->isRunning()) {
throw new RuntimeException('Process is already running');
}
$process = clone $this;
$process->start($callback, $env);
return $process;
}
/**
* Waits for the process to terminate.
*
* The callback receives the type of output (out or err) and some bytes
* from the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
*
* @param callable|null $callback A valid PHP callback
*
* @return int The exitcode of the process
*
* @throws ProcessTimedOutException When process timed out
* @throws ProcessSignaledException When process stopped after receiving signal
* @throws LogicException When process is not yet started
*/
public function wait(callable $callback = null)
{
$this->requireProcessIsStarted(__FUNCTION__);
$this->updateStatus(false);
if (null !== $callback) {
if (!$this->processPipes->haveReadSupport()) {
$this->stop(0);
throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait"');
}
$this->callback = $this->buildCallback($callback);
}
do {
$this->checkTimeout();
$running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
$this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
} while ($running);
while ($this->isRunning()) {
$this->checkTimeout();
usleep(1000);
}
if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
throw new ProcessSignaledException($this);
}
return $this->exitcode;
}
/**
* Waits until the callback returns true.
*
* The callback receives the type of output (out or err) and some bytes
* from the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
*
* @throws RuntimeException When process timed out
* @throws LogicException When process is not yet started
* @throws ProcessTimedOutException In case the timeout was reached
*/
public function waitUntil(callable $callback): bool
{
$this->requireProcessIsStarted(__FUNCTION__);
$this->updateStatus(false);
if (!$this->processPipes->haveReadSupport()) {
$this->stop(0);
throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
}
$callback = $this->buildCallback($callback);
$ready = false;
while (true) {
$this->checkTimeout();
$running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
$output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
foreach ($output as $type => $data) {
if (3 !== $type) {
$ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
} elseif (!isset($this->fallbackStatus['signaled'])) {
$this->fallbackStatus['exitcode'] = (int) $data;
}
}
if ($ready) {
return true;
}
if (!$running) {
return false;
}
usleep(1000);
}
}
/**
* Returns the Pid (process identifier), if applicable.
*
* @return int|null The process id if running, null otherwise
*/
public function getPid()
{
return $this->isRunning() ? $this->processInformation['pid'] : null;
}
/**
* Sends a POSIX signal to the process.
*
* @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
*
* @return $this
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
* @throws RuntimeException In case of failure
*/
public function signal($signal)
{
$this->doSignal($signal, true);
return $this;
}
/**
* Disables fetching output and error output from the underlying process.
*
* @return $this
*
* @throws RuntimeException In case the process is already running
* @throws LogicException if an idle timeout is set
*/
public function disableOutput()
{
if ($this->isRunning()) {
throw new RuntimeException('Disabling output while the process is running is not possible.');
}
if (null !== $this->idleTimeout) {
throw new LogicException('Output can not be disabled while an idle timeout is set.');
}
$this->outputDisabled = true;
return $this;
}
/**
* Enables fetching output and error output from the underlying process.
*
* @return $this
*
* @throws RuntimeException In case the process is already running
*/
public function enableOutput()
{
if ($this->isRunning()) {
throw new RuntimeException('Enabling output while the process is running is not possible.');
}
$this->outputDisabled = false;
return $this;
}
/**
* Returns true in case the output is disabled, false otherwise.
*
* @return bool
*/
public function isOutputDisabled()
{
return $this->outputDisabled;
}
/**
* Returns the current output of the process (STDOUT).
*
* @return string The process output
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getOutput()
{
$this->readPipesForOutput(__FUNCTION__);
if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
return '';
}
return $ret;
}
/**
* Returns the output incrementally.
*
* In comparison with the getOutput method which always return the whole
* output, this one returns the new output since the last call.
*
* @return string The process output since the last call
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getIncrementalOutput()
{
$this->readPipesForOutput(__FUNCTION__);
$latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
$this->incrementalOutputOffset = ftell($this->stdout);
if (false === $latest) {
return '';
}
return $latest;
}
/**
* Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
*
* @param int $flags A bit field of Process::ITER_* flags
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*
* @return \Generator
*/
public function getIterator($flags = 0)
{
$this->readPipesForOutput(__FUNCTION__, false);
$clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
$blocking = !(self::ITER_NON_BLOCKING & $flags);
$yieldOut = !(self::ITER_SKIP_OUT & $flags);
$yieldErr = !(self::ITER_SKIP_ERR & $flags);
while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
if ($yieldOut) {
$out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
if (isset($out[0])) {
if ($clearOutput) {
$this->clearOutput();
} else {
$this->incrementalOutputOffset = ftell($this->stdout);
}
yield self::OUT => $out;
}
}
if ($yieldErr) {
$err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
if (isset($err[0])) {
if ($clearOutput) {
$this->clearErrorOutput();
} else {
$this->incrementalErrorOutputOffset = ftell($this->stderr);
}
yield self::ERR => $err;
}
}
if (!$blocking && !isset($out[0]) && !isset($err[0])) {
yield self::OUT => '';
}
$this->checkTimeout();
$this->readPipesForOutput(__FUNCTION__, $blocking);
}
}
/**
* Clears the process output.
*
* @return $this
*/
public function clearOutput()
{
ftruncate($this->stdout, 0);
fseek($this->stdout, 0);
$this->incrementalOutputOffset = 0;
return $this;
}
/**
* Returns the current error output of the process (STDERR).
*
* @return string The process error output
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getErrorOutput()
{
$this->readPipesForOutput(__FUNCTION__);
if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
return '';
}
return $ret;
}
/**
* Returns the errorOutput incrementally.
*
* In comparison with the getErrorOutput method which always return the
* whole error output, this one returns the new error output since the last
* call.
*
* @return string The process error output since the last call
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getIncrementalErrorOutput()
{
$this->readPipesForOutput(__FUNCTION__);
$latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
$this->incrementalErrorOutputOffset = ftell($this->stderr);
if (false === $latest) {
return '';
}
return $latest;
}
/**
* Clears the process output.
*
* @return $this
*/
public function clearErrorOutput()
{
ftruncate($this->stderr, 0);
fseek($this->stderr, 0);
$this->incrementalErrorOutputOffset = 0;
return $this;
}
/**
* Returns the exit code returned by the process.
*
* @return int|null The exit status code, null if the Process is not terminated
*/
public function getExitCode()
{
$this->updateStatus(false);
return $this->exitcode;
}
/**
* Returns a string representation for the exit code returned by the process.
*
* This method relies on the Unix exit code status standardization
* and might not be relevant for other operating systems.
*
* @return string|null A string representation for the exit status code, null if the Process is not terminated
*
* @see http://tldp.org/LDP/abs/html/exitcodes.html
* @see http://en.wikipedia.org/wiki/Unix_signal
*/
public function getExitCodeText()
{
if (null === $exitcode = $this->getExitCode()) {
return null;
}
return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
}
/**
* Checks if the process ended successfully.
*
* @return bool true if the process ended successfully, false otherwise
*/
public function isSuccessful()
{
return 0 === $this->getExitCode();
}
/**
* Returns true if the child process has been terminated by an uncaught signal.
*
* It always returns false on Windows.
*
* @return bool
*
* @throws LogicException In case the process is not terminated
*/
public function hasBeenSignaled()
{
$this->requireProcessIsTerminated(__FUNCTION__);
return $this->processInformation['signaled'];
}
/**
* Returns the number of the signal that caused the child process to terminate its execution.
*
* It is only meaningful if hasBeenSignaled() returns true.
*
* @return int
*
* @throws RuntimeException In case --enable-sigchild is activated
* @throws LogicException In case the process is not terminated
*/
public function getTermSignal()
{
$this->requireProcessIsTerminated(__FUNCTION__);
if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
}
return $this->processInformation['termsig'];
}
/**
* Returns true if the child process has been stopped by a signal.
*
* It always returns false on Windows.
*
* @return bool
*
* @throws LogicException In case the process is not terminated
*/
public function hasBeenStopped()
{
$this->requireProcessIsTerminated(__FUNCTION__);
return $this->processInformation['stopped'];
}
/**
* Returns the number of the signal that caused the child process to stop its execution.
*
* It is only meaningful if hasBeenStopped() returns true.
*
* @return int
*
* @throws LogicException In case the process is not terminated
*/
public function getStopSignal()
{
$this->requireProcessIsTerminated(__FUNCTION__);
return $this->processInformation['stopsig'];
}
/**
* Checks if the process is currently running.
*
* @return bool true if the process is currently running, false otherwise
*/
public function isRunning()
{
if (self::STATUS_STARTED !== $this->status) {
return false;
}
$this->updateStatus(false);
return $this->processInformation['running'];
}
/**
* Checks if the process has been started with no regard to the current state.
*
* @return bool true if status is ready, false otherwise
*/
public function isStarted()
{
return self::STATUS_READY != $this->status;
}
/**
* Checks if the process is terminated.
*
* @return bool true if process is terminated, false otherwise
*/
public function isTerminated()
{
$this->updateStatus(false);
return self::STATUS_TERMINATED == $this->status;
}
/**
* Gets the process status.
*
* The status is one of: ready, started, terminated.
*
* @return string The current process status
*/
public function getStatus()
{
$this->updateStatus(false);
return $this->status;
}
/**
* Stops the process.
*
* @param int|float $timeout The timeout in seconds
* @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
*
* @return int|null The exit-code of the process or null if it's not running
*/
public function stop($timeout = 10, $signal = null)
{
$timeoutMicro = microtime(true) + $timeout;
if ($this->isRunning()) {
// given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here
$this->doSignal(15, false);
do {
usleep(1000);
} while ($this->isRunning() && microtime(true) < $timeoutMicro);
if ($this->isRunning()) {
// Avoid exception here: process is supposed to be running, but it might have stopped just
// after this line. In any case, let's silently discard the error, we cannot do anything.
$this->doSignal($signal ?: 9, false);
}
}
if ($this->isRunning()) {
if (isset($this->fallbackStatus['pid'])) {
unset($this->fallbackStatus['pid']);
return $this->stop(0, $signal);
}
$this->close();
}
return $this->exitcode;
}
/**
* Adds a line to the STDOUT stream.
*
* @internal
*/
public function addOutput(string $line)
{
$this->lastOutputTime = microtime(true);
fseek($this->stdout, 0, SEEK_END);
fwrite($this->stdout, $line);
fseek($this->stdout, $this->incrementalOutputOffset);
}
/**
* Adds a line to the STDERR stream.
*
* @internal
*/
public function addErrorOutput(string $line)
{
$this->lastOutputTime = microtime(true);
fseek($this->stderr, 0, SEEK_END);
fwrite($this->stderr, $line);
fseek($this->stderr, $this->incrementalErrorOutputOffset);
}
/**
* Gets the last output time in seconds.
*
* @return float|null The last output time in seconds or null if it isn't started
*/
public function getLastOutputTime(): ?float
{
return $this->lastOutputTime;
}
/**
* Gets the command line to be executed.
*
* @return string The command to execute
*/
public function getCommandLine()
{
return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
}
/**
* Sets the command line to be executed.
*
* @param string|array $commandline The command to execute
*
* @return $this
*
* @deprecated since Symfony 4.2.
*/
public function setCommandLine($commandline)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.2.', __METHOD__), E_USER_DEPRECATED);
$this->commandline = $commandline;
return $this;
}
/**
* Gets the process timeout (max. runtime).
*
* @return float|null The timeout in seconds or null if it's disabled
*/
public function getTimeout()
{
return $this->timeout;
}
/**
* Gets the process idle timeout (max. time since last output).
*
* @return float|null The timeout in seconds or null if it's disabled
*/
public function getIdleTimeout()
{
return $this->idleTimeout;
}
/**
* Sets the process timeout (max. runtime) in seconds.
*
* To disable the timeout, set this value to null.
*
* @param int|float|null $timeout The timeout in seconds
*
* @return $this
*
* @throws InvalidArgumentException if the timeout is negative
*/
public function setTimeout($timeout)
{
$this->timeout = $this->validateTimeout($timeout);
return $this;
}
/**
* Sets the process idle timeout (max. time since last output).
*
* To disable the timeout, set this value to null.
*
* @param int|float|null $timeout The timeout in seconds
*
* @return $this
*
* @throws LogicException if the output is disabled
* @throws InvalidArgumentException if the timeout is negative
*/
public function setIdleTimeout($timeout)
{
if (null !== $timeout && $this->outputDisabled) {
throw new LogicException('Idle timeout can not be set while the output is disabled.');
}
$this->idleTimeout = $this->validateTimeout($timeout);
return $this;
}
/**
* Enables or disables the TTY mode.
*
* @param bool $tty True to enabled and false to disable
*
* @return $this
*
* @throws RuntimeException In case the TTY mode is not supported
*/
public function setTty($tty)
{
if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
throw new RuntimeException('TTY mode is not supported on Windows platform.');
}
if ($tty && !self::isTtySupported()) {
throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
}
$this->tty = (bool) $tty;
return $this;
}
/**
* Checks if the TTY mode is enabled.
*
* @return bool true if the TTY mode is enabled, false otherwise
*/
public function isTty()
{
return $this->tty;
}
/**
* Sets PTY mode.
*
* @param bool $bool
*
* @return $this
*/
public function setPty($bool)
{
$this->pty = (bool) $bool;
return $this;
}
/**
* Returns PTY state.
*
* @return bool
*/
public function isPty()
{
return $this->pty;
}
/**
* Gets the working directory.
*
* @return string|null The current working directory or null on failure
*/
public function getWorkingDirectory()
{
if (null === $this->cwd) {
// getcwd() will return false if any one of the parent directories does not have
// the readable or search mode set, even if the current directory does
return getcwd() ?: null;
}
return $this->cwd;
}
/**
* Sets the current working directory.
*
* @param string $cwd The new working directory
*
* @return $this
*/
public function setWorkingDirectory($cwd)
{
$this->cwd = $cwd;
return $this;
}
/**
* Gets the environment variables.
*
* @return array The current environment variables
*/
public function getEnv()
{
return $this->env;
}
/**
* Sets the environment variables.
*
* Each environment variable value should be a string.
* If it is an array, the variable is ignored.
* If it is false or null, it will be removed when
* env vars are otherwise inherited.
*
* That happens in PHP when 'argv' is registered into
* the $_ENV array for instance.
*
* @param array $env The new environment variables
*
* @return $this
*/
public function setEnv(array $env)
{
// Process can not handle env values that are arrays
$env = array_filter($env, function ($value) {
return !\is_array($value);
});
$this->env = $env;
return $this;
}
/**
* Gets the Process input.
*
* @return resource|string|\Iterator|null The Process input
*/
public function getInput()
{
return $this->input;
}
/**
* Sets the input.
*
* This content will be passed to the underlying process standard input.
*
* @param string|int|float|bool|resource|\Traversable|null $input The content
*
* @return $this
*
* @throws LogicException In case the process is running
*/
public function setInput($input)
{
if ($this->isRunning()) {
throw new LogicException('Input can not be set while the process is running.');
}
$this->input = ProcessUtils::validateInput(__METHOD__, $input);
return $this;
}
/**
* Sets whether environment variables will be inherited or not.
*
* @param bool $inheritEnv
*
* @return $this
*
* @deprecated since Symfony 4.4, env variables are always inherited
*/
public function inheritEnvironmentVariables($inheritEnv = true)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.4, env variables are always inherited.', __METHOD__), E_USER_DEPRECATED);
if (!$inheritEnv) {
throw new InvalidArgumentException('Not inheriting environment variables is not supported.');
}
return $this;
}
/**
* Performs a check between the timeout definition and the time the process started.
*
* In case you run a background process (with the start method), you should
* trigger this method regularly to ensure the process timeout
*
* @throws ProcessTimedOutException In case the timeout was reached
*/
public function checkTimeout()
{
if (self::STATUS_STARTED !== $this->status) {
return;
}
if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
$this->stop(0);
throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
}
if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
$this->stop(0);
throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
}
}
/**
* Returns whether TTY is supported on the current operating system.
*/
public static function isTtySupported(): bool
{
static $isTtySupported;
if (null === $isTtySupported) {
$isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
}
return $isTtySupported;
}
/**
* Returns whether PTY is supported on the current operating system.
*
* @return bool
*/
public static function isPtySupported()
{
static $result;
if (null !== $result) {
return $result;
}
if ('\\' === \DIRECTORY_SEPARATOR) {
return $result = false;
}
return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
}
/**
* Creates the descriptors needed by the proc_open.
*/
private function getDescriptors(): array
{
if ($this->input instanceof \Iterator) {
$this->input->rewind();
}
if ('\\' === \DIRECTORY_SEPARATOR) {
$this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
} else {
$this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
}
return $this->processPipes->getDescriptors();
}
/**
* Builds up the callback used by wait().
*
* The callbacks adds all occurred output to the specific buffer and calls
* the user callback (if present) with the received output.
*
* @param callable|null $callback The user defined PHP callback
*
* @return \Closure A PHP closure
*/
protected function buildCallback(callable $callback = null)
{
if ($this->outputDisabled) {
return function ($type, $data) use ($callback): bool {
return null !== $callback && $callback($type, $data);
};
}
$out = self::OUT;
return function ($type, $data) use ($callback, $out): bool {
if ($out == $type) {
$this->addOutput($data);
} else {
$this->addErrorOutput($data);
}
return null !== $callback && $callback($type, $data);
};
}
/**
* Updates the status of the process, reads pipes.
*
* @param bool $blocking Whether to use a blocking read call
*/
protected function updateStatus($blocking)
{
if (self::STATUS_STARTED !== $this->status) {
return;
}
$this->processInformation = proc_get_status($this->process);
$running = $this->processInformation['running'];
$this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
if ($this->fallbackStatus && $this->isSigchildEnabled()) {
$this->processInformation = $this->fallbackStatus + $this->processInformation;
}
if (!$running) {
$this->close();
}
}
/**
* Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
*
* @return bool
*/
protected function isSigchildEnabled()
{
if (null !== self::$sigchild) {
return self::$sigchild;
}
if (!\function_exists('phpinfo')) {
return self::$sigchild = false;
}
ob_start();
phpinfo(INFO_GENERAL);
return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
}
/**
* Reads pipes for the freshest output.
*
* @param string $caller The name of the method that needs fresh outputs
* @param bool $blocking Whether to use blocking calls or not
*
* @throws LogicException in case output has been disabled or process is not started
*/
private function readPipesForOutput(string $caller, bool $blocking = false)
{
if ($this->outputDisabled) {
throw new LogicException('Output has been disabled.');
}
$this->requireProcessIsStarted($caller);
$this->updateStatus($blocking);
}
/**
* Validates and returns the filtered timeout.
*
* @throws InvalidArgumentException if the given timeout is a negative number
*/
private function validateTimeout(?float $timeout): ?float
{
$timeout = (float) $timeout;
if (0.0 === $timeout) {
$timeout = null;
} elseif ($timeout < 0) {
throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
}
return $timeout;
}
/**
* Reads pipes, executes callback.
*
* @param bool $blocking Whether to use blocking calls or not
* @param bool $close Whether to close file handles or not
*/
private function readPipes(bool $blocking, bool $close)
{
$result = $this->processPipes->readAndWrite($blocking, $close);
$callback = $this->callback;
foreach ($result as $type => $data) {
if (3 !== $type) {
$callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
} elseif (!isset($this->fallbackStatus['signaled'])) {
$this->fallbackStatus['exitcode'] = (int) $data;
}
}
}
/**
* Closes process resource, closes file handles, sets the exitcode.
*
* @return int The exitcode
*/
private function close(): int
{
$this->processPipes->close();
if (\is_resource($this->process)) {
proc_close($this->process);
}
$this->exitcode = $this->processInformation['exitcode'];
$this->status = self::STATUS_TERMINATED;
if (-1 === $this->exitcode) {
if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
// if process has been signaled, no exitcode but a valid termsig, apply Unix convention
$this->exitcode = 128 + $this->processInformation['termsig'];
} elseif ($this->isSigchildEnabled()) {
$this->processInformation['signaled'] = true;
$this->processInformation['termsig'] = -1;
}
}
// Free memory from self-reference callback created by buildCallback
// Doing so in other contexts like __destruct or by garbage collector is ineffective
// Now pipes are closed, so the callback is no longer necessary
$this->callback = null;
return $this->exitcode;
}
/**
* Resets data related to the latest run of the process.
*/
private function resetProcessData()
{
$this->starttime = null;
$this->callback = null;
$this->exitcode = null;
$this->fallbackStatus = [];
$this->processInformation = null;
$this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
$this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
$this->process = null;
$this->latestSignal = null;
$this->status = self::STATUS_READY;
$this->incrementalOutputOffset = 0;
$this->incrementalErrorOutputOffset = 0;
}
/**
* Sends a POSIX signal to the process.
*
* @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
* @param bool $throwException Whether to throw exception in case signal failed
*
* @return bool True if the signal was sent successfully, false otherwise
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
* @throws RuntimeException In case of failure
*/
private function doSignal(int $signal, bool $throwException): bool
{
if (null === $pid = $this->getPid()) {
if ($throwException) {
throw new LogicException('Can not send signal on a non running process.');
}
return false;
}
if ('\\' === \DIRECTORY_SEPARATOR) {
exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
if ($exitCode && $this->isRunning()) {
if ($throwException) {
throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
}
return false;
}
} else {
if (!$this->isSigchildEnabled()) {
$ok = @proc_terminate($this->process, $signal);
} elseif (\function_exists('posix_kill')) {
$ok = @posix_kill($pid, $signal);
} elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
$ok = false === fgets($pipes[2]);
}
if (!$ok) {
if ($throwException) {
throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal));
}
return false;
}
}
$this->latestSignal = $signal;
$this->fallbackStatus['signaled'] = true;
$this->fallbackStatus['exitcode'] = -1;
$this->fallbackStatus['termsig'] = $this->latestSignal;
return true;
}
private function prepareWindowsCommandLine(string $cmd, array &$env): string
{
$uid = uniqid('', true);
$varCount = 0;
$varCache = [];
$cmd = preg_replace_callback(
'/"(?:(
[^"%!^]*+
(?:
(?: !LF! | "(?:\^[%!^])?+" )
[^"%!^]*+
)++
) | [^"]*+ )"/x',
function ($m) use (&$env, &$varCache, &$varCount, $uid) {
if (!isset($m[1])) {
return $m[0];
}
if (isset($varCache[$m[0]])) {
return $varCache[$m[0]];
}
if (false !== strpos($value = $m[1], "\0")) {
$value = str_replace("\0", '?', $value);
}
if (false === strpbrk($value, "\"%!\n")) {
return '"'.$value.'"';
}
$value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
$value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
$var = $uid.++$varCount;
$env[$var] = $value;
return $varCache[$m[0]] = '!'.$var.'!';
},
$cmd
);
$cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
foreach ($this->processPipes->getFiles() as $offset => $filename) {
$cmd .= ' '.$offset.'>"'.$filename.'"';
}
return $cmd;
}
/**
* Ensures the process is running or terminated, throws a LogicException if the process has a not started.
*
* @throws LogicException if the process has not run
*/
private function requireProcessIsStarted(string $functionName)
{
if (!$this->isStarted()) {
throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
}
}
/**
* Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated".
*
* @throws LogicException if the process is not yet terminated
*/
private function requireProcessIsTerminated(string $functionName)
{
if (!$this->isTerminated()) {
throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
}
}
/**
* Escapes a string to be used as a shell argument.
*/
private function escapeArgument(?string $argument): string
{
if ('' === $argument || null === $argument) {
return '""';
}
if ('\\' !== \DIRECTORY_SEPARATOR) {
return "'".str_replace("'", "'\\''", $argument)."'";
}
if (false !== strpos($argument, "\0")) {
$argument = str_replace("\0", '?', $argument);
}
if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
return $argument;
}
$argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
}
private function replacePlaceholders(string $commandline, array $env)
{
return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) {
if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) {
throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": %s.', $matches[1], $commandline));
}
return $this->escapeArgument($env[$matches[1]]);
}, $commandline);
}
private function getDefaultEnv(): array
{
$env = [];
foreach ($_SERVER as $k => $v) {
if (\is_string($v) && false !== $v = getenv($k)) {
$env[$k] = $v;
}
}
foreach ($_ENV as $k => $v) {
if (\is_string($v)) {
$env[$k] = $v;
}
}
return $env;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
/**
* Contains all events dispatched by an Application.
*
* @author Francesco Levorato <git@flevour.net>
*/
final class ConsoleEvents
{
/**
* The COMMAND event allows you to attach listeners before any command is
* executed by the console. It also allows you to modify the command, input and output
* before they are handled to the command.
*
* @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
*/
const COMMAND = 'console.command';
/**
* The TERMINATE event allows you to attach listeners after a command is
* executed by the console.
*
* @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
*/
const TERMINATE = 'console.terminate';
/**
* The ERROR event occurs when an uncaught exception or error appears.
*
* This event allows you to deal with the exception/error or
* to modify the thrown exception.
*
* @Event("Symfony\Component\Console\Event\ConsoleErrorEvent")
*/
const ERROR = 'console.error';
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Debug\ErrorHandler as LegacyErrorHandler;
use Symfony\Component\Debug\Exception\FatalThrowableError;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
use Symfony\Contracts\Service\ResetInterface;
/**
* An Application is the container for a collection of commands.
*
* It is the main entry point of a Console application.
*
* This class is optimized for a standard CLI environment.
*
* Usage:
*
* $app = new Application('myapp', '1.0 (stable)');
* $app->add(new SimpleCommand());
* $app->run();
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Application implements ResetInterface
{
private $commands = [];
private $wantHelps = false;
private $runningCommand;
private $name;
private $version;
private $commandLoader;
private $catchExceptions = true;
private $autoExit = true;
private $definition;
private $helperSet;
private $dispatcher;
private $terminal;
private $defaultCommand;
private $singleCommand = false;
private $initialized;
/**
* @param string $name The name of the application
* @param string $version The version of the application
*/
public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
{
$this->name = $name;
$this->version = $version;
$this->terminal = new Terminal();
$this->defaultCommand = 'list';
}
/**
* @final since Symfony 4.3, the type-hint will be updated to the interface from symfony/contracts in 5.0
*/
public function setDispatcher(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher);
}
public function setCommandLoader(CommandLoaderInterface $commandLoader)
{
$this->commandLoader = $commandLoader;
}
/**
* Runs the current application.
*
* @return int 0 if everything went fine, or an error code
*
* @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
*/
public function run(InputInterface $input = null, OutputInterface $output = null)
{
putenv('LINES='.$this->terminal->getHeight());
putenv('COLUMNS='.$this->terminal->getWidth());
if (null === $input) {
$input = new ArgvInput();
}
if (null === $output) {
$output = new ConsoleOutput();
}
$renderException = function (\Throwable $e) use ($output) {
if ($output instanceof ConsoleOutputInterface) {
$this->renderThrowable($e, $output->getErrorOutput());
} else {
$this->renderThrowable($e, $output);
}
};
if ($phpHandler = set_exception_handler($renderException)) {
restore_exception_handler();
if (!\is_array($phpHandler) || (!$phpHandler[0] instanceof ErrorHandler && !$phpHandler[0] instanceof LegacyErrorHandler)) {
$errorHandler = true;
} elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
$phpHandler[0]->setExceptionHandler($errorHandler);
}
}
$this->configureIO($input, $output);
try {
$exitCode = $this->doRun($input, $output);
} catch (\Exception $e) {
if (!$this->catchExceptions) {
throw $e;
}
$renderException($e);
$exitCode = $e->getCode();
if (is_numeric($exitCode)) {
$exitCode = (int) $exitCode;
if (0 === $exitCode) {
$exitCode = 1;
}
} else {
$exitCode = 1;
}
} finally {
// if the exception handler changed, keep it
// otherwise, unregister $renderException
if (!$phpHandler) {
if (set_exception_handler($renderException) === $renderException) {
restore_exception_handler();
}
restore_exception_handler();
} elseif (!$errorHandler) {
$finalHandler = $phpHandler[0]->setExceptionHandler(null);
if ($finalHandler !== $renderException) {
$phpHandler[0]->setExceptionHandler($finalHandler);
}
}
}
if ($this->autoExit) {
if ($exitCode > 255) {
$exitCode = 255;
}
exit($exitCode);
}
return $exitCode;
}
/**
* Runs the current application.
*
* @return int 0 if everything went fine, or an error code
*/
public function doRun(InputInterface $input, OutputInterface $output)
{
if (true === $input->hasParameterOption(['--version', '-V'], true)) {
$output->writeln($this->getLongVersion());
return 0;
}
try {
// Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
$input->bind($this->getDefinition());
} catch (ExceptionInterface $e) {
// Errors must be ignored, full binding/validation happens later when the command is known.
}
$name = $this->getCommandName($input);
if (true === $input->hasParameterOption(['--help', '-h'], true)) {
if (!$name) {
$name = 'help';
$input = new ArrayInput(['command_name' => $this->defaultCommand]);
} else {
$this->wantHelps = true;
}
}
if (!$name) {
$name = $this->defaultCommand;
$definition = $this->getDefinition();
$definition->setArguments(array_merge(
$definition->getArguments(),
[
'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
]
));
}
try {
$this->runningCommand = null;
// the command name MUST be the first element of the input
$command = $this->find($name);
} catch (\Throwable $e) {
if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
if (0 === $event->getExitCode()) {
return 0;
}
$e = $event->getError();
}
throw $e;
}
$alternative = $alternatives[0];
$style = new SymfonyStyle($input, $output);
$style->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error');
if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
return $event->getExitCode();
}
return 1;
}
$command = $this->find($alternative);
}
$this->runningCommand = $command;
$exitCode = $this->doRunCommand($command, $input, $output);
$this->runningCommand = null;
return $exitCode;
}
/**
* {@inheritdoc}
*/
public function reset()
{
}
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
}
/**
* Get the helper set associated with the command.
*
* @return HelperSet The HelperSet instance associated with this command
*/
public function getHelperSet()
{
if (!$this->helperSet) {
$this->helperSet = $this->getDefaultHelperSet();
}
return $this->helperSet;
}
public function setDefinition(InputDefinition $definition)
{
$this->definition = $definition;
}
/**
* Gets the InputDefinition related to this Application.
*
* @return InputDefinition The InputDefinition instance
*/
public function getDefinition()
{
if (!$this->definition) {
$this->definition = $this->getDefaultInputDefinition();
}
if ($this->singleCommand) {
$inputDefinition = $this->definition;
$inputDefinition->setArguments();
return $inputDefinition;
}
return $this->definition;
}
/**
* Gets the help message.
*
* @return string A help message
*/
public function getHelp()
{
return $this->getLongVersion();
}
/**
* Gets whether to catch exceptions or not during commands execution.
*
* @return bool Whether to catch exceptions or not during commands execution
*/
public function areExceptionsCaught()
{
return $this->catchExceptions;
}
/**
* Sets whether to catch exceptions or not during commands execution.
*
* @param bool $boolean Whether to catch exceptions or not during commands execution
*/
public function setCatchExceptions($boolean)
{
$this->catchExceptions = (bool) $boolean;
}
/**
* Gets whether to automatically exit after a command execution or not.
*
* @return bool Whether to automatically exit after a command execution or not
*/
public function isAutoExitEnabled()
{
return $this->autoExit;
}
/**
* Sets whether to automatically exit after a command execution or not.
*
* @param bool $boolean Whether to automatically exit after a command execution or not
*/
public function setAutoExit($boolean)
{
$this->autoExit = (bool) $boolean;
}
/**
* Gets the name of the application.
*
* @return string The application name
*/
public function getName()
{
return $this->name;
}
/**
* Sets the application name.
*
* @param string $name The application name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Gets the application version.
*
* @return string The application version
*/
public function getVersion()
{
return $this->version;
}
/**
* Sets the application version.
*
* @param string $version The application version
*/
public function setVersion($version)
{
$this->version = $version;
}
/**
* Returns the long version of the application.
*
* @return string The long application version
*/
public function getLongVersion()
{
if ('UNKNOWN' !== $this->getName()) {
if ('UNKNOWN' !== $this->getVersion()) {
return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
}
return $this->getName();
}
return 'Console Tool';
}
/**
* Registers a new command.
*
* @param string $name The command name
*
* @return Command The newly created command
*/
public function register($name)
{
return $this->add(new Command($name));
}
/**
* Adds an array of command objects.
*
* If a Command is not enabled it will not be added.
*
* @param Command[] $commands An array of commands
*/
public function addCommands(array $commands)
{
foreach ($commands as $command) {
$this->add($command);
}
}
/**
* Adds a command object.
*
* If a command with the same name already exists, it will be overridden.
* If the command is not enabled it will not be added.
*
* @return Command|null The registered command if enabled or null
*/
public function add(Command $command)
{
$this->init();
$command->setApplication($this);
if (!$command->isEnabled()) {
$command->setApplication(null);
return null;
}
// Will throw if the command is not correctly initialized.
$command->getDefinition();
if (!$command->getName()) {
throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', \get_class($command)));
}
$this->commands[$command->getName()] = $command;
foreach ($command->getAliases() as $alias) {
$this->commands[$alias] = $command;
}
return $command;
}
/**
* Returns a registered command by name or alias.
*
* @param string $name The command name or alias
*
* @return Command A Command object
*
* @throws CommandNotFoundException When given command name does not exist
*/
public function get($name)
{
$this->init();
if (!$this->has($name)) {
throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
}
$command = $this->commands[$name];
if ($this->wantHelps) {
$this->wantHelps = false;
$helpCommand = $this->get('help');
$helpCommand->setCommand($command);
return $helpCommand;
}
return $command;
}
/**
* Returns true if the command exists, false otherwise.
*
* @param string $name The command name or alias
*
* @return bool true if the command exists, false otherwise
*/
public function has($name)
{
$this->init();
return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name)));
}
/**
* Returns an array of all unique namespaces used by currently registered commands.
*
* It does not return the global namespace which always exists.
*
* @return string[] An array of namespaces
*/
public function getNamespaces()
{
$namespaces = [];
foreach ($this->all() as $command) {
if ($command->isHidden()) {
continue;
}
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
foreach ($command->getAliases() as $alias) {
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
}
}
return array_values(array_unique(array_filter($namespaces)));
}
/**
* Finds a registered namespace by a name or an abbreviation.
*
* @param string $namespace A namespace or abbreviation to search for
*
* @return string A registered namespace
*
* @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
*/
public function findNamespace($namespace)
{
$allNamespaces = $this->getNamespaces();
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace);
$namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);
if (empty($namespaces)) {
$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
if (1 == \count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
}
$message .= implode("\n ", $alternatives);
}
throw new NamespaceNotFoundException($message, $alternatives);
}
$exact = \in_array($namespace, $namespaces, true);
if (\count($namespaces) > 1 && !$exact) {
throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
}
return $exact ? $namespace : reset($namespaces);
}
/**
* Finds a command by name or alias.
*
* Contrary to get, this command tries to find the best
* match if you give it an abbreviation of a name or alias.
*
* @param string $name A command name or a command alias
*
* @return Command A Command instance
*
* @throws CommandNotFoundException When command name is incorrect or ambiguous
*/
public function find($name)
{
$this->init();
$aliases = [];
foreach ($this->commands as $command) {
foreach ($command->getAliases() as $alias) {
if (!$this->has($alias)) {
$this->commands[$alias] = $command;
}
}
}
if ($this->has($name)) {
return $this->get($name);
}
$allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
$commands = preg_grep('{^'.$expr.'}', $allCommands);
if (empty($commands)) {
$commands = preg_grep('{^'.$expr.'}i', $allCommands);
}
// if no commands matched or we just matched namespaces
if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {
if (false !== $pos = strrpos($name, ':')) {
// check if a namespace exists and contains commands
$this->findNamespace(substr($name, 0, $pos));
}
$message = sprintf('Command "%s" is not defined.', $name);
if ($alternatives = $this->findAlternatives($name, $allCommands)) {
// remove hidden commands
$alternatives = array_filter($alternatives, function ($name) {
return !$this->get($name)->isHidden();
});
if (1 == \count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
}
$message .= implode("\n ", $alternatives);
}
throw new CommandNotFoundException($message, array_values($alternatives));
}
// filter out aliases for commands which are already on the list
if (\count($commands) > 1) {
$commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
$commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) {
if (!$commandList[$nameOrAlias] instanceof Command) {
$commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
}
$commandName = $commandList[$nameOrAlias]->getName();
$aliases[$nameOrAlias] = $commandName;
return $commandName === $nameOrAlias || !\in_array($commandName, $commands);
}));
}
if (\count($commands) > 1) {
$usableWidth = $this->terminal->getWidth() - 10;
$abbrevs = array_values($commands);
$maxLen = 0;
foreach ($abbrevs as $abbrev) {
$maxLen = max(Helper::strlen($abbrev), $maxLen);
}
$abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) {
if ($commandList[$cmd]->isHidden()) {
unset($commands[array_search($cmd, $commands)]);
return false;
}
$abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
}, array_values($commands));
if (\count($commands) > 1) {
$suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));
throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s", $name, $suggestions), array_values($commands));
}
}
$command = $this->get(reset($commands));
if ($command->isHidden()) {
@trigger_error(sprintf('Command "%s" is hidden, finding it using an abbreviation is deprecated since Symfony 4.4, use its full name instead.', $command->getName()), E_USER_DEPRECATED);
}
return $command;
}
/**
* Gets the commands (registered in the given namespace if provided).
*
* The array keys are the full names and the values the command instances.
*
* @param string $namespace A namespace name
*
* @return Command[] An array of Command instances
*/
public function all($namespace = null)
{
$this->init();
if (null === $namespace) {
if (!$this->commandLoader) {
return $this->commands;
}
$commands = $this->commands;
foreach ($this->commandLoader->getNames() as $name) {
if (!isset($commands[$name]) && $this->has($name)) {
$commands[$name] = $this->get($name);
}
}
return $commands;
}
$commands = [];
foreach ($this->commands as $name => $command) {
if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
$commands[$name] = $command;
}
}
if ($this->commandLoader) {
foreach ($this->commandLoader->getNames() as $name) {
if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
$commands[$name] = $this->get($name);
}
}
}
return $commands;
}
/**
* Returns an array of possible abbreviations given a set of names.
*
* @param array $names An array of names
*
* @return array An array of abbreviations
*/
public static function getAbbreviations($names)
{
$abbrevs = [];
foreach ($names as $name) {
for ($len = \strlen($name); $len > 0; --$len) {
$abbrev = substr($name, 0, $len);
$abbrevs[$abbrev][] = $name;
}
}
return $abbrevs;
}
/**
* Renders a caught exception.
*
* @deprecated since Symfony 4.4, use "renderThrowable()" instead
*/
public function renderException(\Exception $e, OutputInterface $output)
{
@trigger_error(sprintf('The "%s::renderException()" method is deprecated since Symfony 4.4, use "renderThrowable()" instead.', __CLASS__), E_USER_DEPRECATED);
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
$this->doRenderException($e, $output);
$this->finishRenderThrowableOrException($output);
}
public function renderThrowable(\Throwable $e, OutputInterface $output): void
{
if (__CLASS__ !== \get_class($this) && __CLASS__ === (new \ReflectionMethod($this, 'renderThrowable'))->getDeclaringClass()->getName() && __CLASS__ !== (new \ReflectionMethod($this, 'renderException'))->getDeclaringClass()->getName()) {
@trigger_error(sprintf('The "%s::renderException()" method is deprecated since Symfony 4.4, use "renderThrowable()" instead.', __CLASS__), E_USER_DEPRECATED);
if (!$e instanceof \Exception) {
$e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
}
$this->renderException($e, $output);
return;
}
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
$this->doRenderThrowable($e, $output);
$this->finishRenderThrowableOrException($output);
}
private function finishRenderThrowableOrException(OutputInterface $output): void
{
if (null !== $this->runningCommand) {
$output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
}
}
/**
* @deprecated since Symfony 4.4, use "doRenderThrowable()" instead
*/
protected function doRenderException(\Exception $e, OutputInterface $output)
{
@trigger_error(sprintf('The "%s::doRenderException()" method is deprecated since Symfony 4.4, use "doRenderThrowable()" instead.', __CLASS__), E_USER_DEPRECATED);
$this->doActuallyRenderThrowable($e, $output);
}
protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void
{
if (__CLASS__ !== \get_class($this) && __CLASS__ === (new \ReflectionMethod($this, 'doRenderThrowable'))->getDeclaringClass()->getName() && __CLASS__ !== (new \ReflectionMethod($this, 'doRenderException'))->getDeclaringClass()->getName()) {
@trigger_error(sprintf('The "%s::doRenderException()" method is deprecated since Symfony 4.4, use "doRenderThrowable()" instead.', __CLASS__), E_USER_DEPRECATED);
if (!$e instanceof \Exception) {
$e = class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
}
$this->doRenderException($e, $output);
return;
}
$this->doActuallyRenderThrowable($e, $output);
}
private function doActuallyRenderThrowable(\Throwable $e, OutputInterface $output): void
{
do {
$message = trim($e->getMessage());
if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$class = \get_class($e);
$class = 'c' === $class[0] && 0 === strpos($class, "class@anonymous\0") ? get_parent_class($class).'@anonymous' : $class;
$title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
$len = Helper::strlen($title);
} else {
$len = 0;
}
if (false !== strpos($message, "class@anonymous\0")) {
$message = preg_replace_callback('/class@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
return class_exists($m[0], false) ? get_parent_class($m[0]).'@anonymous' : $m[0];
}, $message);
}
$width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : PHP_INT_MAX;
$lines = [];
foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) {
foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
// pre-format lines to get the right string length
$lineLength = Helper::strlen($line) + 4;
$lines[] = [$line, $lineLength];
$len = max($lineLength, $len);
}
}
$messages = [];
if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
}
$messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::strlen($title))));
}
foreach ($lines as $line) {
$messages[] = sprintf('<error> %s %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
}
$messages[] = $emptyLine;
$messages[] = '';
$output->writeln($messages, OutputInterface::VERBOSITY_QUIET);
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
// exception related properties
$trace = $e->getTrace();
array_unshift($trace, [
'function' => '',
'file' => $e->getFile() ?: 'n/a',
'line' => $e->getLine() ?: 'n/a',
'args' => [],
]);
for ($i = 0, $count = \count($trace); $i < $count; ++$i) {
$class = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
$function = isset($trace[$i]['function']) ? $trace[$i]['function'] : '';
$file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
$line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a';
$output->writeln(sprintf(' %s%s at <info>%s:%s</info>', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET);
}
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
}
} while ($e = $e->getPrevious());
}
/**
* Configures the input and output instances based on the user arguments and options.
*/
protected function configureIO(InputInterface $input, OutputInterface $output)
{
if (true === $input->hasParameterOption(['--ansi'], true)) {
$output->setDecorated(true);
} elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {
$output->setDecorated(false);
}
if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {
$input->setInteractive(false);
} elseif (\function_exists('posix_isatty')) {
$inputStream = null;
if ($input instanceof StreamableInputInterface) {
$inputStream = $input->getStream();
}
if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
$input->setInteractive(false);
}
}
switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break;
case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break;
case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break;
case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break;
default: $shellVerbosity = 0; break;
}
if (true === $input->hasParameterOption(['--quiet', '-q'], true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
$shellVerbosity = -1;
} else {
if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
$shellVerbosity = 3;
} elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
$shellVerbosity = 2;
} elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
$shellVerbosity = 1;
}
}
if (-1 === $shellVerbosity) {
$input->setInteractive(false);
}
putenv('SHELL_VERBOSITY='.$shellVerbosity);
$_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
$_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
}
/**
* Runs the current command.
*
* If an event dispatcher has been attached to the application,
* events are also dispatched during the life-cycle of the command.
*
* @return int 0 if everything went fine, or an error code
*/
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
{
foreach ($command->getHelperSet() as $helper) {
if ($helper instanceof InputAwareInterface) {
$helper->setInput($input);
}
}
if (null === $this->dispatcher) {
return $command->run($input, $output);
}
// bind before the console.command event, so the listeners have access to input options/arguments
try {
$command->mergeApplicationDefinition();
$input->bind($command->getDefinition());
} catch (ExceptionInterface $e) {
// ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
}
$event = new ConsoleCommandEvent($command, $input, $output);
$e = null;
try {
$this->dispatcher->dispatch($event, ConsoleEvents::COMMAND);
if ($event->commandShouldRun()) {
$exitCode = $command->run($input, $output);
} else {
$exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
}
} catch (\Throwable $e) {
$event = new ConsoleErrorEvent($input, $output, $e, $command);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
$e = $event->getError();
if (0 === $exitCode = $event->getExitCode()) {
$e = null;
}
}
$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
$this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);
if (null !== $e) {
throw $e;
}
return $event->getExitCode();
}
/**
* Gets the name of the command based on input.
*
* @return string|null
*/
protected function getCommandName(InputInterface $input)
{
return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
}
/**
* Gets the default input definition.
*
* @return InputDefinition An InputDefinition instance
*/
protected function getDefaultInputDefinition()
{
return new InputDefinition([
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
]);
}
/**
* Gets the default commands that should always be available.
*
* @return Command[] An array of default Command instances
*/
protected function getDefaultCommands()
{
return [new HelpCommand(), new ListCommand()];
}
/**
* Gets the default helper set with the helpers that should always be available.
*
* @return HelperSet A HelperSet instance
*/
protected function getDefaultHelperSet()
{
return new HelperSet([
new FormatterHelper(),
new DebugFormatterHelper(),
new ProcessHelper(),
new QuestionHelper(),
]);
}
/**
* Returns abbreviated suggestions in string format.
*/
private function getAbbreviationSuggestions(array $abbrevs): string
{
return ' '.implode("\n ", $abbrevs);
}
/**
* Returns the namespace part of the command name.
*
* This method is not part of public API and should not be used directly.
*
* @param string $name The full name of the command
* @param string $limit The maximum number of parts of the namespace
*
* @return string The namespace of the command
*/
public function extractNamespace($name, $limit = null)
{
$parts = explode(':', $name, -1);
return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));
}
/**
* Finds alternative of $name among $collection,
* if nothing is found in $collection, try in $abbrevs.
*
* @return string[] A sorted array of similar string
*/
private function findAlternatives(string $name, iterable $collection): array
{
$threshold = 1e3;
$alternatives = [];
$collectionParts = [];
foreach ($collection as $item) {
$collectionParts[$item] = explode(':', $item);
}
foreach (explode(':', $name) as $i => $subname) {
foreach ($collectionParts as $collectionName => $parts) {
$exists = isset($alternatives[$collectionName]);
if (!isset($parts[$i]) && $exists) {
$alternatives[$collectionName] += $threshold;
continue;
} elseif (!isset($parts[$i])) {
continue;
}
$lev = levenshtein($subname, $parts[$i]);
if ($lev <= \strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
$alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
} elseif ($exists) {
$alternatives[$collectionName] += $threshold;
}
}
}
foreach ($collection as $item) {
$lev = levenshtein($name, $item);
if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) {
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
}
}
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE);
return array_keys($alternatives);
}
/**
* Sets the default Command name.
*
* @param string $commandName The Command name
* @param bool $isSingleCommand Set to true if there is only one command in this application
*
* @return self
*/
public function setDefaultCommand($commandName, $isSingleCommand = false)
{
$this->defaultCommand = $commandName;
if ($isSingleCommand) {
// Ensure the command exist
$this->find($commandName);
$this->singleCommand = true;
}
return $this;
}
/**
* @internal
*/
public function isSingleCommand(): bool
{
return $this->singleCommand;
}
private function splitStringByWidth(string $string, int $width): array
{
// str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
// additionally, array_slice() is not enough as some character has doubled width.
// we need a function to split string not by character count but by string width
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return str_split($string, $width);
}
$utf8String = mb_convert_encoding($string, 'utf8', $encoding);
$lines = [];
$line = '';
$offset = 0;
while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) {
$offset += \strlen($m[0]);
foreach (preg_split('//u', $m[0]) as $char) {
// test if $char could be appended to current line
if (mb_strwidth($line.$char, 'utf8') <= $width) {
$line .= $char;
continue;
}
// if not, push current line to array and make new line
$lines[] = str_pad($line, $width);
$line = $char;
}
}
$lines[] = \count($lines) ? str_pad($line, $width) : $line;
mb_convert_variables($encoding, 'utf8', $lines);
return $lines;
}
/**
* Returns all namespaces of the command name.
*
* @return string[] The namespaces of the command
*/
private function extractAllNamespaces(string $name): array
{
// -1 as third argument is needed to skip the command short name when exploding
$parts = explode(':', $name, -1);
$namespaces = [];
foreach ($parts as $part) {
if (\count($namespaces)) {
$namespaces[] = end($namespaces).':'.$part;
} else {
$namespaces[] = $part;
}
}
return $namespaces;
}
private function init()
{
if ($this->initialized) {
return;
}
$this->initialized = true;
foreach ($this->getDefaultCommands() as $command) {
$this->add($command);
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Logger;
use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* PSR-3 compliant console logger.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @see https://www.php-fig.org/psr/psr-3/
*/
class ConsoleLogger extends AbstractLogger
{
const INFO = 'info';
const ERROR = 'error';
private $output;
private $verbosityLevelMap = [
LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
];
private $formatLevelMap = [
LogLevel::EMERGENCY => self::ERROR,
LogLevel::ALERT => self::ERROR,
LogLevel::CRITICAL => self::ERROR,
LogLevel::ERROR => self::ERROR,
LogLevel::WARNING => self::INFO,
LogLevel::NOTICE => self::INFO,
LogLevel::INFO => self::INFO,
LogLevel::DEBUG => self::INFO,
];
private $errored = false;
public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = [])
{
$this->output = $output;
$this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
$this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
}
/**
* {@inheritdoc}
*
* @return void
*/
public function log($level, $message, array $context = [])
{
if (!isset($this->verbosityLevelMap[$level])) {
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
}
$output = $this->output;
// Write to the error output if necessary and available
if (self::ERROR === $this->formatLevelMap[$level]) {
if ($this->output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->errored = true;
}
// the if condition check isn't necessary -- it's the same one that $output will do internally anyway.
// We only do it for efficiency here as the message formatting is relatively expensive.
if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) {
$output->writeln(sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]);
}
}
/**
* Returns true when any messages have been logged at error levels.
*
* @return bool
*/
public function hasErrored()
{
return $this->errored;
}
/**
* Interpolates context values into the message placeholders.
*
* @author PHP Framework Interoperability Group
*/
private function interpolate(string $message, array $context): string
{
if (false === strpos($message, '{')) {
return $message;
}
$replacements = [];
foreach ($context as $key => $val) {
if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) {
$replacements["{{$key}}"] = $val;
} elseif ($val instanceof \DateTimeInterface) {
$replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);
} elseif (\is_object($val)) {
$replacements["{{$key}}"] = '[object '.\get_class($val).']';
} else {
$replacements["{{$key}}"] = '['.\gettype($val).']';
}
}
return strtr($message, $replacements);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
/**
* StreamableInputInterface is the interface implemented by all input classes
* that have an input stream.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
interface StreamableInputInterface extends InputInterface
{
/**
* Sets the input stream to read from when interacting with the user.
*
* This is mainly useful for testing purpose.
*
* @param resource $stream The input stream
*/
public function setStream($stream);
/**
* Returns the input stream.
*
* @return resource|null
*/
public function getStream();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\InvalidOptionException;
/**
* ArrayInput represents an input provided as an array.
*
* Usage:
*
* $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ArrayInput extends Input
{
private $parameters;
public function __construct(array $parameters, InputDefinition $definition = null)
{
$this->parameters = $parameters;
parent::__construct($definition);
}
/**
* {@inheritdoc}
*/
public function getFirstArgument()
{
foreach ($this->parameters as $param => $value) {
if ($param && \is_string($param) && '-' === $param[0]) {
continue;
}
return $value;
}
return null;
}
/**
* {@inheritdoc}
*/
public function hasParameterOption($values, $onlyParams = false)
{
$values = (array) $values;
foreach ($this->parameters as $k => $v) {
if (!\is_int($k)) {
$v = $k;
}
if ($onlyParams && '--' === $v) {
return false;
}
if (\in_array($v, $values)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function getParameterOption($values, $default = false, $onlyParams = false)
{
$values = (array) $values;
foreach ($this->parameters as $k => $v) {
if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) {
return $default;
}
if (\is_int($k)) {
if (\in_array($v, $values)) {
return true;
}
} elseif (\in_array($k, $values)) {
return $v;
}
}
return $default;
}
/**
* Returns a stringified representation of the args passed to the command.
*
* @return string
*/
public function __toString()
{
$params = [];
foreach ($this->parameters as $param => $val) {
if ($param && \is_string($param) && '-' === $param[0]) {
if (\is_array($val)) {
foreach ($val as $v) {
$params[] = $param.('' != $v ? '='.$this->escapeToken($v) : '');
}
} else {
$params[] = $param.('' != $val ? '='.$this->escapeToken($val) : '');
}
} else {
$params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val);
}
}
return implode(' ', $params);
}
/**
* {@inheritdoc}
*/
protected function parse()
{
foreach ($this->parameters as $key => $value) {
if ('--' === $key) {
return;
}
if (0 === strpos($key, '--')) {
$this->addLongOption(substr($key, 2), $value);
} elseif (0 === strpos($key, '-')) {
$this->addShortOption(substr($key, 1), $value);
} else {
$this->addArgument($key, $value);
}
}
}
/**
* Adds a short option value.
*
* @throws InvalidOptionException When option given doesn't exist
*/
private function addShortOption(string $shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut));
}
$this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
}
/**
* Adds a long option value.
*
* @throws InvalidOptionException When option given doesn't exist
* @throws InvalidOptionException When a required value is missing
*/
private function addLongOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name));
}
$option = $this->definition->getOption($name);
if (null === $value) {
if ($option->isValueRequired()) {
throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name));
}
if (!$option->isValueOptional()) {
$value = true;
}
}
$this->options[$name] = $value;
}
/**
* Adds an argument value.
*
* @param string|int $name The argument name
* @param mixed $value The value for the argument
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
private function addArgument($name, $value)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$this->arguments[$name] = $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* ArgvInput represents an input coming from the CLI arguments.
*
* Usage:
*
* $input = new ArgvInput();
*
* By default, the `$_SERVER['argv']` array is used for the input values.
*
* This can be overridden by explicitly passing the input values in the constructor:
*
* $input = new ArgvInput($_SERVER['argv']);
*
* If you pass it yourself, don't forget that the first element of the array
* is the name of the running application.
*
* When passing an argument to the constructor, be sure that it respects
* the same rules as the argv one. It's almost always better to use the
* `StringInput` when you want to provide your own input.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
* @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
*/
class ArgvInput extends Input
{
private $tokens;
private $parsed;
/**
* @param array|null $argv An array of parameters from the CLI (in the argv format)
*/
public function __construct(array $argv = null, InputDefinition $definition = null)
{
if (null === $argv) {
$argv = $_SERVER['argv'];
}
// strip the application name
array_shift($argv);
$this->tokens = $argv;
parent::__construct($definition);
}
protected function setTokens(array $tokens)
{
$this->tokens = $tokens;
}
/**
* {@inheritdoc}
*/
protected function parse()
{
$parseOptions = true;
$this->parsed = $this->tokens;
while (null !== $token = array_shift($this->parsed)) {
if ($parseOptions && '' == $token) {
$this->parseArgument($token);
} elseif ($parseOptions && '--' == $token) {
$parseOptions = false;
} elseif ($parseOptions && 0 === strpos($token, '--')) {
$this->parseLongOption($token);
} elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
$this->parseShortOption($token);
} else {
$this->parseArgument($token);
}
}
}
/**
* Parses a short option.
*/
private function parseShortOption(string $token)
{
$name = substr($token, 1);
if (\strlen($name) > 1) {
if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
// an option with a value (with no space)
$this->addShortOption($name[0], substr($name, 1));
} else {
$this->parseShortOptionSet($name);
}
} else {
$this->addShortOption($name, null);
}
}
/**
* Parses a short option set.
*
* @throws RuntimeException When option given doesn't exist
*/
private function parseShortOptionSet(string $name)
{
$len = \strlen($name);
for ($i = 0; $i < $len; ++$i) {
if (!$this->definition->hasShortcut($name[$i])) {
$encoding = mb_detect_encoding($name, null, true);
throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding)));
}
$option = $this->definition->getOptionForShortcut($name[$i]);
if ($option->acceptValue()) {
$this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
break;
} else {
$this->addLongOption($option->getName(), null);
}
}
}
/**
* Parses a long option.
*/
private function parseLongOption(string $token)
{
$name = substr($token, 2);
if (false !== $pos = strpos($name, '=')) {
if (0 === \strlen($value = substr($name, $pos + 1))) {
array_unshift($this->parsed, $value);
}
$this->addLongOption(substr($name, 0, $pos), $value);
} else {
$this->addLongOption($name, null);
}
}
/**
* Parses an argument.
*
* @throws RuntimeException When too many arguments are given
*/
private function parseArgument(string $token)
{
$c = \count($this->arguments);
// if input is expecting another argument, add it
if ($this->definition->hasArgument($c)) {
$arg = $this->definition->getArgument($c);
$this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;
// if last argument isArray(), append token to last argument
} elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
$arg = $this->definition->getArgument($c - 1);
$this->arguments[$arg->getName()][] = $token;
// unexpected argument
} else {
$all = $this->definition->getArguments();
if (\count($all)) {
throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all))));
}
throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token));
}
}
/**
* Adds a short option value.
*
* @throws RuntimeException When option given doesn't exist
*/
private function addShortOption(string $shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
}
$this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
}
/**
* Adds a long option value.
*
* @throws RuntimeException When option given doesn't exist
*/
private function addLongOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
}
$option = $this->definition->getOption($name);
if (null !== $value && !$option->acceptValue()) {
throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
}
if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) {
// if option accepts an optional or mandatory argument
// let's see if there is one provided
$next = array_shift($this->parsed);
if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) {
$value = $next;
} else {
array_unshift($this->parsed, $next);
}
}
if (null === $value) {
if ($option->isValueRequired()) {
throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name));
}
if (!$option->isArray() && !$option->isValueOptional()) {
$value = true;
}
}
if ($option->isArray()) {
$this->options[$name][] = $value;
} else {
$this->options[$name] = $value;
}
}
/**
* {@inheritdoc}
*/
public function getFirstArgument()
{
$isOption = false;
foreach ($this->tokens as $i => $token) {
if ($token && '-' === $token[0]) {
if (false !== strpos($token, '=') || !isset($this->tokens[$i + 1])) {
continue;
}
// If it's a long option, consider that everything after "--" is the option name.
// Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator)
$name = '-' === $token[1] ? substr($token, 2) : substr($token, -1);
if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) {
// noop
} elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) {
$isOption = true;
}
continue;
}
if ($isOption) {
$isOption = false;
continue;
}
return $token;
}
return null;
}
/**
* {@inheritdoc}
*/
public function hasParameterOption($values, $onlyParams = false)
{
$values = (array) $values;
foreach ($this->tokens as $token) {
if ($onlyParams && '--' === $token) {
return false;
}
foreach ($values as $value) {
// Options with values:
// For long options, test for '--option=' at beginning
// For short options, test for '-o' at beginning
$leading = 0 === strpos($value, '--') ? $value.'=' : $value;
if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) {
return true;
}
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function getParameterOption($values, $default = false, $onlyParams = false)
{
$values = (array) $values;
$tokens = $this->tokens;
while (0 < \count($tokens)) {
$token = array_shift($tokens);
if ($onlyParams && '--' === $token) {
return $default;
}
foreach ($values as $value) {
if ($token === $value) {
return array_shift($tokens);
}
// Options with values:
// For long options, test for '--option=' at beginning
// For short options, test for '-o' at beginning
$leading = 0 === strpos($value, '--') ? $value.'=' : $value;
if ('' !== $leading && 0 === strpos($token, $leading)) {
return substr($token, \strlen($leading));
}
}
}
return $default;
}
/**
* Returns a stringified representation of the args passed to the command.
*
* @return string
*/
public function __toString()
{
$tokens = array_map(function ($token) {
if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
return $match[1].$this->escapeToken($match[2]);
}
if ($token && '-' !== $token[0]) {
return $this->escapeToken($token);
}
return $token;
}, $this->tokens);
return implode(' ', $tokens);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Represents a command line argument.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputArgument
{
const REQUIRED = 1;
const OPTIONAL = 2;
const IS_ARRAY = 4;
private $name;
private $mode;
private $default;
private $description;
/**
* @param string $name The argument name
* @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @param string $description A description text
* @param string|string[]|null $default The default value (for self::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function __construct(string $name, int $mode = null, string $description = '', $default = null)
{
if (null === $mode) {
$mode = self::OPTIONAL;
} elseif ($mode > 7 || $mode < 1) {
throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
}
$this->name = $name;
$this->mode = $mode;
$this->description = $description;
$this->setDefault($default);
}
/**
* Returns the argument name.
*
* @return string The argument name
*/
public function getName()
{
return $this->name;
}
/**
* Returns true if the argument is required.
*
* @return bool true if parameter mode is self::REQUIRED, false otherwise
*/
public function isRequired()
{
return self::REQUIRED === (self::REQUIRED & $this->mode);
}
/**
* Returns true if the argument can take multiple values.
*
* @return bool true if mode is self::IS_ARRAY, false otherwise
*/
public function isArray()
{
return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
}
/**
* Sets the default value.
*
* @param string|string[]|null $default The default value
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault($default = null)
{
if (self::REQUIRED === $this->mode && null !== $default) {
throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
}
if ($this->isArray()) {
if (null === $default) {
$default = [];
} elseif (!\is_array($default)) {
throw new LogicException('A default value for an array argument must be an array.');
}
}
$this->default = $default;
}
/**
* Returns the default value.
*
* @return string|string[]|null The default value
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns the description text.
*
* @return string The description text
*/
public function getDescription()
{
return $this->description;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
/**
* InputAwareInterface should be implemented by classes that depends on the
* Console Input.
*
* @author Wouter J <waldio.webdesign@gmail.com>
*/
interface InputAwareInterface
{
/**
* Sets the Console Input.
*/
public function setInput(InputInterface $input);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Represents a command line option.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputOption
{
const VALUE_NONE = 1;
const VALUE_REQUIRED = 2;
const VALUE_OPTIONAL = 4;
const VALUE_IS_ARRAY = 8;
private $name;
private $shortcut;
private $mode;
private $default;
private $description;
/**
* @param string $name The option name
* @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int|null $mode The option mode: One of the VALUE_* constants
* @param string $description A description text
* @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null)
{
if (0 === strpos($name, '--')) {
$name = substr($name, 2);
}
if (empty($name)) {
throw new InvalidArgumentException('An option name cannot be empty.');
}
if (empty($shortcut)) {
$shortcut = null;
}
if (null !== $shortcut) {
if (\is_array($shortcut)) {
$shortcut = implode('|', $shortcut);
}
$shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
$shortcuts = array_filter($shortcuts);
$shortcut = implode('|', $shortcuts);
if (empty($shortcut)) {
throw new InvalidArgumentException('An option shortcut cannot be empty.');
}
}
if (null === $mode) {
$mode = self::VALUE_NONE;
} elseif ($mode > 15 || $mode < 1) {
throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
}
$this->name = $name;
$this->shortcut = $shortcut;
$this->mode = $mode;
$this->description = $description;
if ($this->isArray() && !$this->acceptValue()) {
throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
}
$this->setDefault($default);
}
/**
* Returns the option shortcut.
*
* @return string|null The shortcut
*/
public function getShortcut()
{
return $this->shortcut;
}
/**
* Returns the option name.
*
* @return string The name
*/
public function getName()
{
return $this->name;
}
/**
* Returns true if the option accepts a value.
*
* @return bool true if value mode is not self::VALUE_NONE, false otherwise
*/
public function acceptValue()
{
return $this->isValueRequired() || $this->isValueOptional();
}
/**
* Returns true if the option requires a value.
*
* @return bool true if value mode is self::VALUE_REQUIRED, false otherwise
*/
public function isValueRequired()
{
return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
}
/**
* Returns true if the option takes an optional value.
*
* @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise
*/
public function isValueOptional()
{
return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
}
/**
* Returns true if the option can take multiple values.
*
* @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise
*/
public function isArray()
{
return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
}
/**
* Sets the default value.
*
* @param string|string[]|int|bool|null $default The default value
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault($default = null)
{
if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
}
if ($this->isArray()) {
if (null === $default) {
$default = [];
} elseif (!\is_array($default)) {
throw new LogicException('A default value for an array option must be an array.');
}
}
$this->default = $this->acceptValue() ? $default : false;
}
/**
* Returns the default value.
*
* @return string|string[]|int|bool|null The default value
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns the description text.
*
* @return string The description text
*/
public function getDescription()
{
return $this->description;
}
/**
* Checks whether the given option equals this one.
*
* @return bool
*/
public function equals(self $option)
{
return $option->getName() === $this->getName()
&& $option->getShortcut() === $this->getShortcut()
&& $option->getDefault() === $this->getDefault()
&& $option->isArray() === $this->isArray()
&& $option->isValueRequired() === $this->isValueRequired()
&& $option->isValueOptional() === $this->isValueOptional()
;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* A InputDefinition represents a set of valid command line arguments and options.
*
* Usage:
*
* $definition = new InputDefinition([
* new InputArgument('name', InputArgument::REQUIRED),
* new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
* ]);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputDefinition
{
private $arguments;
private $requiredCount;
private $hasAnArrayArgument = false;
private $hasOptional;
private $options;
private $shortcuts;
/**
* @param array $definition An array of InputArgument and InputOption instance
*/
public function __construct(array $definition = [])
{
$this->setDefinition($definition);
}
/**
* Sets the definition of the input.
*/
public function setDefinition(array $definition)
{
$arguments = [];
$options = [];
foreach ($definition as $item) {
if ($item instanceof InputOption) {
$options[] = $item;
} else {
$arguments[] = $item;
}
}
$this->setArguments($arguments);
$this->setOptions($options);
}
/**
* Sets the InputArgument objects.
*
* @param InputArgument[] $arguments An array of InputArgument objects
*/
public function setArguments($arguments = [])
{
$this->arguments = [];
$this->requiredCount = 0;
$this->hasOptional = false;
$this->hasAnArrayArgument = false;
$this->addArguments($arguments);
}
/**
* Adds an array of InputArgument objects.
*
* @param InputArgument[] $arguments An array of InputArgument objects
*/
public function addArguments($arguments = [])
{
if (null !== $arguments) {
foreach ($arguments as $argument) {
$this->addArgument($argument);
}
}
}
/**
* @throws LogicException When incorrect argument is given
*/
public function addArgument(InputArgument $argument)
{
if (isset($this->arguments[$argument->getName()])) {
throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
}
if ($this->hasAnArrayArgument) {
throw new LogicException('Cannot add an argument after an array argument.');
}
if ($argument->isRequired() && $this->hasOptional) {
throw new LogicException('Cannot add a required argument after an optional one.');
}
if ($argument->isArray()) {
$this->hasAnArrayArgument = true;
}
if ($argument->isRequired()) {
++$this->requiredCount;
} else {
$this->hasOptional = true;
}
$this->arguments[$argument->getName()] = $argument;
}
/**
* Returns an InputArgument by name or by position.
*
* @param string|int $name The InputArgument name or position
*
* @return InputArgument An InputArgument object
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function getArgument($name)
{
if (!$this->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;
return $arguments[$name];
}
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
*
* @return bool true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name)
{
$arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;
return isset($arguments[$name]);
}
/**
* Gets the array of InputArgument objects.
*
* @return InputArgument[] An array of InputArgument objects
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Returns the number of InputArguments.
*
* @return int The number of InputArguments
*/
public function getArgumentCount()
{
return $this->hasAnArrayArgument ? PHP_INT_MAX : \count($this->arguments);
}
/**
* Returns the number of required InputArguments.
*
* @return int The number of required InputArguments
*/
public function getArgumentRequiredCount()
{
return $this->requiredCount;
}
/**
* Gets the default values.
*
* @return array An array of default values
*/
public function getArgumentDefaults()
{
$values = [];
foreach ($this->arguments as $argument) {
$values[$argument->getName()] = $argument->getDefault();
}
return $values;
}
/**
* Sets the InputOption objects.
*
* @param InputOption[] $options An array of InputOption objects
*/
public function setOptions($options = [])
{
$this->options = [];
$this->shortcuts = [];
$this->addOptions($options);
}
/**
* Adds an array of InputOption objects.
*
* @param InputOption[] $options An array of InputOption objects
*/
public function addOptions($options = [])
{
foreach ($options as $option) {
$this->addOption($option);
}
}
/**
* @throws LogicException When option given already exist
*/
public function addOption(InputOption $option)
{
if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
}
if ($option->getShortcut()) {
foreach (explode('|', $option->getShortcut()) as $shortcut) {
if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) {
throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut));
}
}
}
$this->options[$option->getName()] = $option;
if ($option->getShortcut()) {
foreach (explode('|', $option->getShortcut()) as $shortcut) {
$this->shortcuts[$shortcut] = $option->getName();
}
}
}
/**
* Returns an InputOption by name.
*
* @param string $name The InputOption name
*
* @return InputOption A InputOption object
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function getOption($name)
{
if (!$this->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
}
return $this->options[$name];
}
/**
* Returns true if an InputOption object exists by name.
*
* This method can't be used to check if the user included the option when
* executing the command (use getOption() instead).
*
* @param string $name The InputOption name
*
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasOption($name)
{
return isset($this->options[$name]);
}
/**
* Gets the array of InputOption objects.
*
* @return InputOption[] An array of InputOption objects
*/
public function getOptions()
{
return $this->options;
}
/**
* Returns true if an InputOption object exists by shortcut.
*
* @param string $name The InputOption shortcut
*
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasShortcut($name)
{
return isset($this->shortcuts[$name]);
}
/**
* Gets an InputOption by shortcut.
*
* @param string $shortcut The Shortcut name
*
* @return InputOption An InputOption object
*/
public function getOptionForShortcut($shortcut)
{
return $this->getOption($this->shortcutToName($shortcut));
}
/**
* Gets an array of default values.
*
* @return array An array of all default values
*/
public function getOptionDefaults()
{
$values = [];
foreach ($this->options as $option) {
$values[$option->getName()] = $option->getDefault();
}
return $values;
}
/**
* Returns the InputOption name given a shortcut.
*
* @throws InvalidArgumentException When option given does not exist
*
* @internal
*/
public function shortcutToName(string $shortcut): string
{
if (!isset($this->shortcuts[$shortcut])) {
throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
}
return $this->shortcuts[$shortcut];
}
/**
* Gets the synopsis.
*
* @param bool $short Whether to return the short version (with options folded) or not
*
* @return string The synopsis
*/
public function getSynopsis($short = false)
{
$elements = [];
if ($short && $this->getOptions()) {
$elements[] = '[options]';
} elseif (!$short) {
foreach ($this->getOptions() as $option) {
$value = '';
if ($option->acceptValue()) {
$value = sprintf(
' %s%s%s',
$option->isValueOptional() ? '[' : '',
strtoupper($option->getName()),
$option->isValueOptional() ? ']' : ''
);
}
$shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
$elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value);
}
}
if (\count($elements) && $this->getArguments()) {
$elements[] = '[--]';
}
$tail = '';
foreach ($this->getArguments() as $argument) {
$element = '<'.$argument->getName().'>';
if ($argument->isArray()) {
$element .= '...';
}
if (!$argument->isRequired()) {
$element = '['.$element;
$tail .= ']';
}
$elements[] = $element;
}
return implode(' ', $elements).$tail;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* StringInput represents an input provided as a string.
*
* Usage:
*
* $input = new StringInput('foo --bar="foobar"');
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class StringInput extends ArgvInput
{
const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';
/**
* @param string $input A string representing the parameters from the CLI
*/
public function __construct(string $input)
{
parent::__construct([]);
$this->setTokens($this->tokenize($input));
}
/**
* Tokenizes a string.
*
* @throws InvalidArgumentException When unable to parse input (should never happen)
*/
private function tokenize(string $input): array
{
$tokens = [];
$length = \strlen($input);
$cursor = 0;
while ($cursor < $length) {
if (preg_match('/\s+/A', $input, $match, null, $cursor)) {
} elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) {
$tokens[] = $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, \strlen($match[3]) - 2)));
} elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes(substr($match[0], 1, \strlen($match[0]) - 2));
} elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) {
$tokens[] = stripcslashes($match[1]);
} else {
// should never happen
throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));
}
$cursor += \strlen($match[0]);
}
return $tokens;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* Input is the base class for all concrete Input classes.
*
* Three concrete classes are provided by default:
*
* * `ArgvInput`: The input comes from the CLI arguments (argv)
* * `StringInput`: The input is provided as a string
* * `ArrayInput`: The input is provided as an array
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Input implements InputInterface, StreamableInputInterface
{
protected $definition;
protected $stream;
protected $options = [];
protected $arguments = [];
protected $interactive = true;
public function __construct(InputDefinition $definition = null)
{
if (null === $definition) {
$this->definition = new InputDefinition();
} else {
$this->bind($definition);
$this->validate();
}
}
/**
* {@inheritdoc}
*/
public function bind(InputDefinition $definition)
{
$this->arguments = [];
$this->options = [];
$this->definition = $definition;
$this->parse();
}
/**
* Processes command line arguments.
*/
abstract protected function parse();
/**
* {@inheritdoc}
*/
public function validate()
{
$definition = $this->definition;
$givenArguments = $this->arguments;
$missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) {
return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
});
if (\count($missingArguments) > 0) {
throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments)));
}
}
/**
* {@inheritdoc}
*/
public function isInteractive()
{
return $this->interactive;
}
/**
* {@inheritdoc}
*/
public function setInteractive($interactive)
{
$this->interactive = (bool) $interactive;
}
/**
* {@inheritdoc}
*/
public function getArguments()
{
return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
}
/**
* {@inheritdoc}
*/
public function getArgument($name)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault();
}
/**
* {@inheritdoc}
*/
public function setArgument($name, $value)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$this->arguments[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function hasArgument($name)
{
return $this->definition->hasArgument($name);
}
/**
* {@inheritdoc}
*/
public function getOptions()
{
return array_merge($this->definition->getOptionDefaults(), $this->options);
}
/**
* {@inheritdoc}
*/
public function getOption($name)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
}
/**
* {@inheritdoc}
*/
public function setOption($name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
$this->options[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function hasOption($name)
{
return $this->definition->hasOption($name);
}
/**
* Escapes a token through escapeshellarg if it contains unsafe chars.
*
* @param string $token
*
* @return string
*/
public function escapeToken($token)
{
return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
}
/**
* {@inheritdoc}
*/
public function setStream($stream)
{
$this->stream = $stream;
}
/**
* {@inheritdoc}
*/
public function getStream()
{
return $this->stream;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* InputInterface is the interface implemented by all input classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface InputInterface
{
/**
* Returns the first argument from the raw parameters (not parsed).
*
* @return string|null The value of the first argument or null otherwise
*/
public function getFirstArgument();
/**
* Returns true if the raw parameters (not parsed) contain a value.
*
* This method is to be used to introspect the input parameters
* before they have been validated. It must be used carefully.
* Does not necessarily return the correct result for short options
* when multiple flags are combined in the same option.
*
* @param string|array $values The values to look for in the raw parameters (can be an array)
* @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal
*
* @return bool true if the value is contained in the raw parameters
*/
public function hasParameterOption($values, $onlyParams = false);
/**
* Returns the value of a raw option (not parsed).
*
* This method is to be used to introspect the input parameters
* before they have been validated. It must be used carefully.
* Does not necessarily return the correct result for short options
* when multiple flags are combined in the same option.
*
* @param string|array $values The value(s) to look for in the raw parameters (can be an array)
* @param mixed $default The default value to return if no result is found
* @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal
*
* @return mixed The option value
*/
public function getParameterOption($values, $default = false, $onlyParams = false);
/**
* Binds the current Input instance with the given arguments and options.
*
* @throws RuntimeException
*/
public function bind(InputDefinition $definition);
/**
* Validates the input.
*
* @throws RuntimeException When not enough arguments are given
*/
public function validate();
/**
* Returns all the given arguments merged with the default values.
*
* @return array
*/
public function getArguments();
/**
* Returns the argument value for a given argument name.
*
* @param string $name The argument name
*
* @return string|string[]|null The argument value
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function getArgument($name);
/**
* Sets an argument value by name.
*
* @param string $name The argument name
* @param string|string[]|null $value The argument value
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function setArgument($name, $value);
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
*
* @return bool true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name);
/**
* Returns all the given options merged with the default values.
*
* @return array
*/
public function getOptions();
/**
* Returns the option value for a given option name.
*
* @param string $name The option name
*
* @return string|string[]|bool|null The option value
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function getOption($name);
/**
* Sets an option value by name.
*
* @param string $name The option name
* @param string|string[]|bool|null $value The option value
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function setOption($name, $value);
/**
* Returns true if an InputOption object exists by name.
*
* @param string $name The InputOption name
*
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasOption($name);
/**
* Is this input means interactive?
*
* @return bool
*/
public function isInteractive();
/**
* Sets the input interactivity.
*
* @param bool $interactive If the input should be interactive
*/
public function setInteractive($interactive);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
class Terminal
{
private static $width;
private static $height;
private static $stty;
/**
* Gets the terminal width.
*
* @return int
*/
public function getWidth()
{
$width = getenv('COLUMNS');
if (false !== $width) {
return (int) trim($width);
}
if (null === self::$width) {
self::initDimensions();
}
return self::$width ?: 80;
}
/**
* Gets the terminal height.
*
* @return int
*/
public function getHeight()
{
$height = getenv('LINES');
if (false !== $height) {
return (int) trim($height);
}
if (null === self::$height) {
self::initDimensions();
}
return self::$height ?: 50;
}
/**
* @internal
*
* @return bool
*/
public static function hasSttyAvailable()
{
if (null !== self::$stty) {
return self::$stty;
}
exec('stty 2>&1', $output, $exitcode);
return self::$stty = 0 === $exitcode;
}
private static function initDimensions()
{
if ('\\' === \DIRECTORY_SEPARATOR) {
if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON')), $matches)) {
// extract [w, H] from "wxh (WxH)"
// or [w, h] from "wxh"
self::$width = (int) $matches[1];
self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
} elseif (!self::hasVt100Support() && self::hasSttyAvailable()) {
// only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash)
// testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT
self::initDimensionsUsingStty();
} elseif (null !== $dimensions = self::getConsoleMode()) {
// extract [w, h] from "wxh"
self::$width = (int) $dimensions[0];
self::$height = (int) $dimensions[1];
}
} else {
self::initDimensionsUsingStty();
}
}
/**
* Returns whether STDOUT has vt100 support (some Windows 10+ configurations).
*/
private static function hasVt100Support(): bool
{
return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w'));
}
/**
* Initializes dimensions using the output of an stty columns line.
*/
private static function initDimensionsUsingStty()
{
if ($sttyString = self::getSttyColumns()) {
if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
// extract [w, h] from "rows h; columns w;"
self::$width = (int) $matches[2];
self::$height = (int) $matches[1];
} elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
// extract [w, h] from "; h rows; w columns"
self::$width = (int) $matches[2];
self::$height = (int) $matches[1];
}
}
}
/**
* Runs and parses mode CON if it's available, suppressing any error output.
*
* @return int[]|null An array composed of the width and the height or null if it could not be parsed
*/
private static function getConsoleMode(): ?array
{
$info = self::readFromProcess('mode CON');
if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
return null;
}
return [(int) $matches[2], (int) $matches[1]];
}
/**
* Runs and parses stty -a if it's available, suppressing any error output.
*/
private static function getSttyColumns(): ?string
{
return self::readFromProcess('stty -a | grep columns');
}
private static function readFromProcess(string $command): ?string
{
if (!\function_exists('proc_open')) {
return null;
}
$descriptorspec = [
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
if (!\is_resource($process)) {
return null;
}
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $info;
}
}
MZ<EFBFBD>ÿÿ¸@躴 Í!¸LÍ!This program cannot be run in DOS mode.
$Æ,Í;§Bž;§Bž;§Bž2ßמ:§Bž2ßÁž-§Bž2߯ž9§Bž2ßÑž?§Bža9ž8§Bž;§Cž§Bž2ßÈž:§Bž2ßÖž:§Bž2ßÓž:§BžRich;§BžPEL¬MoOà 
8 @`@<40><00>"P@ Pp!8!@ Ø.text 
 `.rdataÎ
@@.data<00>0@À.rsrc @@@.relocÌP"@Bj$¸æ@èxjöÿ @ƒeÐð<E280B9>EÐPVÿ @EЃàûPVÿ @<00>MÔÿX @ƒeü<00>EÔPÿ5H @ÿL @YYÿ5\ @<00>EÔPÿ5` @ÿD @YYÈÿP @ƒMüÿ<C3BC>MÔÿT @3ÀèHÃ; 0@uóÃé¬h€@裡l3@Ç$40@ÿ5h3@£40@h$0@h(0@h 0@ÿ  @ƒÄ£00@…À}jè¹YÃjh"@è3Û‰]üd¡p‰]ä¿€3@SVWÿ0 @;Ãt;Æu3öF‰uäëÿ4 @ëÚ3öF¡|3@;Æu
jè\Yë;¡|3@…Àu,‰5|3@hð @hä @è§YY…ÀtÇEüþÿÿÿ¸ÿé݉5<0@¡|3@;Æuhà @hØ @èlYYÇ|3@9]äuSWÿ8 @9Œ3@thŒ3@èƒY…Àt
SjSÿŒ3@¡$0@ ¼ @ÿ5$0@ÿ5(0@ÿ5 0@èþÿÿƒÄ £80@9,0@u7PÿÀ @ ‰MàPQèŽYYÃEà£80@3Û9,0@uPÿh @9<0@uÿœ @ÇEüþÿÿÿ¡80@èûøMZf9@t3ÀëM¡<@<00>@<00>8PE·H<18>ù t<1B>ù uÕƒ¸vÌ3É9ˆøëƒxtv¼3É9ˆè•ÁÁj£,0@ÿp @jÿÿl @YY£„3@£ˆ3@ÿÌ @ t3@ÿˆ @ p3@¡¨ @£x3@èV謃=0@u @ÿ¬ @Yègƒ=0@ÿu jÿÿ° @Y3ÀÃè{éŸýÿÿÿUì<E280B9>ì(£H1@‰ D1@@1@<1@‰581@‰=41@`1@fŒ T1@01@,1@fŒ%(1@fŒ-$1@œ<>X1@E£L1@E£P1@<00>E£\1@‹…àüÿÿǘ0@¡P1@£L0@Ç@0@ ÀÇD0@¡0@‰…Øüÿÿ¡0@‰…Üüÿÿÿ @£<>0@jè?Yjÿ @h!@ÿ$ @ƒ=<3D>0@ujèYh Àÿ( @Pÿ, @ÉÃÿUìE<00>8csmàu*ƒxu$@= t=!t="t=@™uèÐ3À]ÂhH@ÿ @3ÀÃÿ%¤ @jh("@èbÿ5ˆ3@5Œ @ÿÖY‰Eäƒøÿu ÿuÿÄ @Yëgjè’Yƒeüÿ5ˆ3@ÿÖ‰Eäÿ5„3@ÿÖYY‰Eà<45>EàP<C3A0>EäPÿu5l @ÿÖYPèU‰EÜÿuäÿÖ£ˆ3@ÿuàÿփģ„3@ÇEüþÿÿÿè EÜèÃjèÿUìÿuèNÿÿÿ÷ØÀ÷ØYH]ÃÿV¸ü!@¾ü!@Wø;Æs…ÀtÿЃÇ;þrñ_^ÃÿV¸"@¾"@Wø;Æs…ÀtÿЃÇ;þrñ_^Ãÿ%È @ÌÌÌÌÿUìM¸MZf9t3À]ÃA<Á<>8PEuï3Ò¹ f9H”‹Â]ÃÌÌÌÌÌÌÌÌÌÌÌÿUìEH<È·ASV·q3ÒW<C392>D…öv} H ;ùr XÙ;ûr
BƒÀ(;Örè3À_^[]ÃÌÌÌÌÌÌÌÌÌÌÌÌÿUìjþhH"@he@PƒìSVW¡0@1Eø3ÅP<C385>Eðd£‰eèÇEüh@è*ÿÿÿƒÄ…ÀtUE-@Ph@èPÿÿÿƒÄ…Àt;@$Áè÷ЃàÇEüþÿÿÿMðd‰ Y_^[‹å]Ã3Ò=À”ÂÂÃeèÇEüþÿÿÿ3ÀMðd‰ Y_^[‹å]ÃÌÿ%¸ @ÿ%´ @ÌÌhe@dÿ5D$‰l$<10>l$+àSVW¡0@1Eü3ÅP‰eèÿuøEüÇEüþÿÿÿ‰Eø<45>Eðd£ÃMðd‰ Y__^[‹å]QÃÿUìÿuÿuÿu ÿuh‡@h0@èçƒÄÿVhh3öVèÙƒÄ …Àt VVVVVèƒÄ^Ã3ÀÃÿUìƒì¡0@ƒeøƒeüSW¿Næ@»»ÿÿ;Çt …Ãt ÷У0@ë`V<>EøPÿ< @uü3uøÿ @3ðÿ @3ðÿ @<33>EðPÿ @Eô3Eð3ð;÷u¾Oæ@»ë …óu‹ÆÁà ð‰50@÷Ö‰50@^_[ÉÃÿ%t @ÿ%x @ÿ%| @ÿ%€ @ÿ%„ @ÿ%<25> @ÿ%” @ÿ%˜ @ÿ%Ð @Pdÿ5<00>D$ +d$ SVW‰(‹è¡0@3ÅP‰EðÿuüÇEüÿÿÿÿ<C3BF>Eôd£ÃMôd‰ Y__^[‹å]QÃMð3Íè¯÷ÿÿéÝÿÿÿ<C3BF>MÔÿ%T @T$<08>B JÌ3Èè<C388>÷ÿÿJü3Èè†÷ÿÿ¸l"@ésÿÿÿ¸#Ê#Ü#ˆ)r)b)H)4))ú(æ(Ò(´(¬((ž)ú#à$%Ê%&d&®&¤$('Ä'Ö'è'þ'(((6(¦'H(Z(t(†('''''l'^'R'F'>'>(0'¶'¸)@W@Š@¬MoOl€!@0@˜0@bad allocationH0@ð!@RSDSÑŒ³´<>J¨!öÌëLZc:\users\seld\documents\visual studio 2010\Projects\hiddeninp\Release\hiddeninp.pdbeæþÿÿÿÐÿÿÿþÿÿÿ@@þÿÿÿÌÿÿÿþÿÿÿ:@þÿÿÿØÿÿÿþÿÿÿË@ß@ÿÿÿÿÝ@"d"@à"ì# $#ô&D H#(h ¸#Ê#Ü#ˆ)r)b)H)4))ú(æ(Ò(´(¬((ž)ú#à$%Ê%&d&®&¤$('Ä'Ö'è'þ'(((6(¦'H(Z(t(†('''''l'^'R'F'>'>(0'¶'¸)GetConsoleMode·SetConsoleMode;GetStdHandleKERNEL32.dll??$?6DU?$char_traits@D@std@@V?$allocator@D@1@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@AJ?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@AÂ??$getline@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@YAAAV?$basic_istream@DU?$char_traits@D@std@@@0@AAV10@AAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z_??1?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ{??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ³?endl@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@ZMSVCP90.dll_amsg_exitŸ__getmainargs,_cexit|_exitf_XcptFilterÌexit __initenv_initterm_initterm_e<_configthreadlocaleã__setusermatherr _adjust_fdivË__p__commodeÏ__p__fmodej_encode_pointerà__set_app_typeK_crt_debugger_hookC?terminate@@YAXXZMSVCR90.dllæ_unlock__dllonexitv_lock_onexit`_decode_pointers_except_handler4_common _invoke_watson?_controlfp_s½InterlockedExchange!SleepºInterlockedCompareExchange-TerminateProcess©GetCurrentProcess>UnhandledExceptionFilterSetUnhandledExceptionFilterÑIsDebuggerPresentTQueryPerformanceCounterfGetTickCount­GetCurrentThreadIdªGetCurrentProcessIdOGetSystemTimeAsFileTimes__CxxFrameHandler3Næ@»±¿Dÿÿÿÿÿÿÿÿþÿÿÿ$!@ 8Ph  <00> @(äÈCVä(4VS_VERSION_INFO½ïþStringFileInfob040904b0ÊQFileDescriptionReads from stdin without leaking info to the terminal and outputs back to stdout6 FileVersion1, 0, 0, 08 InternalNamehiddeninputPLegalCopyrightJordi Boggiano - 2012HOriginalFilenamehiddeninput.exe: ProductNameHidden Input: ProductVersion1, 0, 0, 0DVarFileInfo$Translation °<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
</dependentAssembly>
</dependency>
</assembly>PAPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDING@00!0/080F0L0T0^0d0n0{0‰0—0¡0¨0®0³0¸0½0Â0È0Ð0ä0ÿ01#1-1@1J1O1T1v1{1„1‰11§1­1´1È1Í1Ó1Û1á1ç1ô1ú12"2*23292A2M2_2j2p2¹2¿2Ç2Î2Ó2Ù2ß2ç2í2ô2û2 333%303N3T3Z3`3f3l3s3z3<7A>3ˆ3<CB86>33<E28093>3¥3­3µ3Á3Ê3Ï3Õ3ß3è3ó3ÿ34444%4;4B444š4¡4¬4²4Æ4Û4æ4þ45!5^5c5„5‰5¨5H6M6_6}66—677 7*7w7|7Á7ä7ñ7ý78 88=8E8P8V8\8b8h8n8t8z8€8œ8â89 $Ü0è0ì01 1t1x12 2@2\2`2h2t20 0<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
/**
* ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
* This adds information about stderr and section output stream.
*
* @author Dariusz Górecki <darek.krk@gmail.com>
*
* @method ConsoleSectionOutput section() Creates a new output section
*/
interface ConsoleOutputInterface extends OutputInterface
{
/**
* Gets the OutputInterface for errors.
*
* @return OutputInterface
*/
public function getErrorOutput();
public function setErrorOutput(OutputInterface $error);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
/**
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class BufferedOutput extends Output
{
private $buffer = '';
/**
* Empties buffer and returns its content.
*
* @return string
*/
public function fetch()
{
$content = $this->buffer;
$this->buffer = '';
return $content;
}
/**
* {@inheritdoc}
*/
protected function doWrite($message, $newline)
{
$this->buffer .= $message;
if ($newline) {
$this->buffer .= PHP_EOL;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* OutputInterface is the interface implemented by all Output classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface OutputInterface
{
const VERBOSITY_QUIET = 16;
const VERBOSITY_NORMAL = 32;
const VERBOSITY_VERBOSE = 64;
const VERBOSITY_VERY_VERBOSE = 128;
const VERBOSITY_DEBUG = 256;
const OUTPUT_NORMAL = 1;
const OUTPUT_RAW = 2;
const OUTPUT_PLAIN = 4;
/**
* Writes a message to the output.
*
* @param string|iterable $messages The message as an iterable of strings or a single string
* @param bool $newline Whether to add a newline
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*/
public function write($messages, $newline = false, $options = 0);
/**
* Writes a message to the output and adds a newline at the end.
*
* @param string|iterable $messages The message as an iterable of strings or a single string
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*/
public function writeln($messages, $options = 0);
/**
* Sets the verbosity of the output.
*
* @param int $level The level of verbosity (one of the VERBOSITY constants)
*/
public function setVerbosity($level);
/**
* Gets the current verbosity of the output.
*
* @return int The current level of verbosity (one of the VERBOSITY constants)
*/
public function getVerbosity();
/**
* Returns whether verbosity is quiet (-q).
*
* @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
*/
public function isQuiet();
/**
* Returns whether verbosity is verbose (-v).
*
* @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
*/
public function isVerbose();
/**
* Returns whether verbosity is very verbose (-vv).
*
* @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
*/
public function isVeryVerbose();
/**
* Returns whether verbosity is debug (-vvv).
*
* @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
*/
public function isDebug();
/**
* Sets the decorated flag.
*
* @param bool $decorated Whether to decorate the messages
*/
public function setDecorated($decorated);
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
*/
public function isDecorated();
public function setFormatter(OutputFormatterInterface $formatter);
/**
* Returns current output formatter instance.
*
* @return OutputFormatterInterface
*/
public function getFormatter();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* Base class for output classes.
*
* There are five levels of verbosity:
*
* * normal: no option passed (normal output)
* * verbose: -v (more output)
* * very verbose: -vv (highly extended output)
* * debug: -vvv (all debug output)
* * quiet: -q (no output)
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Output implements OutputInterface
{
private $verbosity;
private $formatter;
/**
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool $decorated Whether to decorate messages
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null)
{
$this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
$this->formatter = $formatter ?: new OutputFormatter();
$this->formatter->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
$this->formatter = $formatter;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->formatter;
}
/**
* {@inheritdoc}
*/
public function setDecorated($decorated)
{
$this->formatter->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return $this->formatter->isDecorated();
}
/**
* {@inheritdoc}
*/
public function setVerbosity($level)
{
$this->verbosity = (int) $level;
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isQuiet()
{
return self::VERBOSITY_QUIET === $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isVerbose()
{
return self::VERBOSITY_VERBOSE <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
{
return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isDebug()
{
return self::VERBOSITY_DEBUG <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, $options = self::OUTPUT_NORMAL)
{
$this->write($messages, true, $options);
}
/**
* {@inheritdoc}
*/
public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
}
$types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN;
$type = $types & $options ?: self::OUTPUT_NORMAL;
$verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG;
$verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL;
if ($verbosity > $this->getVerbosity()) {
return;
}
foreach ($messages as $message) {
switch ($type) {
case OutputInterface::OUTPUT_NORMAL:
$message = $this->formatter->format($message);
break;
case OutputInterface::OUTPUT_RAW:
break;
case OutputInterface::OUTPUT_PLAIN:
$message = strip_tags($this->formatter->format($message));
break;
}
$this->doWrite($message, $newline);
}
}
/**
* Writes a message to the output.
*
* @param string $message A message to write to the output
* @param bool $newline Whether to add a newline or not
*/
abstract protected function doWrite($message, $newline);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Terminal;
/**
* @author Pierre du Plessis <pdples@gmail.com>
* @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
*/
class ConsoleSectionOutput extends StreamOutput
{
private $content = [];
private $lines = 0;
private $sections;
private $terminal;
/**
* @param resource $stream
* @param ConsoleSectionOutput[] $sections
*/
public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter)
{
parent::__construct($stream, $verbosity, $decorated, $formatter);
array_unshift($sections, $this);
$this->sections = &$sections;
$this->terminal = new Terminal();
}
/**
* Clears previous output for this section.
*
* @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared
*/
public function clear(int $lines = null)
{
if (empty($this->content) || !$this->isDecorated()) {
return;
}
if ($lines) {
array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content
} else {
$lines = $this->lines;
$this->content = [];
}
$this->lines -= $lines;
parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false);
}
/**
* Overwrites the previous output with a new message.
*
* @param array|string $message
*/
public function overwrite($message)
{
$this->clear();
$this->writeln($message);
}
public function getContent(): string
{
return implode('', $this->content);
}
/**
* @internal
*/
public function addContent(string $input)
{
foreach (explode(PHP_EOL, $input) as $lineContent) {
$this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1;
$this->content[] = $lineContent;
$this->content[] = PHP_EOL;
}
}
/**
* {@inheritdoc}
*/
protected function doWrite($message, $newline)
{
if (!$this->isDecorated()) {
parent::doWrite($message, $newline);
return;
}
$erasedContent = $this->popStreamContentUntilCurrentSection();
$this->addContent($message);
parent::doWrite($message, true);
parent::doWrite($erasedContent, false);
}
/**
* At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits
* current section. Then it erases content it crawled through. Optionally, it erases part of current section too.
*/
private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string
{
$numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection;
$erasedContent = [];
foreach ($this->sections as $section) {
if ($section === $this) {
break;
}
$numberOfLinesToClear += $section->lines;
$erasedContent[] = $section->getContent();
}
if ($numberOfLinesToClear > 0) {
// move cursor up n lines
parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false);
// erase to end of screen
parent::doWrite("\x1b[0J", false);
}
return implode('', array_reverse($erasedContent));
}
private function getDisplayLength(string $text): string
{
return Helper::strlenWithoutDecoration($this->getFormatter(), str_replace("\t", ' ', $text));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR.
*
* This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR.
*
* $output = new ConsoleOutput();
*
* This is equivalent to:
*
* $output = new StreamOutput(fopen('php://stdout', 'w'));
* $stdErr = new StreamOutput(fopen('php://stderr', 'w'));
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
private $stderr;
private $consoleSectionOutputs = [];
/**
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null)
{
parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);
$actualDecorated = $this->isDecorated();
$this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter());
if (null === $decorated) {
$this->setDecorated($actualDecorated && $this->stderr->isDecorated());
}
}
/**
* Creates a new output section.
*/
public function section(): ConsoleSectionOutput
{
return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter());
}
/**
* {@inheritdoc}
*/
public function setDecorated($decorated)
{
parent::setDecorated($decorated);
$this->stderr->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
parent::setFormatter($formatter);
$this->stderr->setFormatter($formatter);
}
/**
* {@inheritdoc}
*/
public function setVerbosity($level)
{
parent::setVerbosity($level);
$this->stderr->setVerbosity($level);
}
/**
* {@inheritdoc}
*/
public function getErrorOutput()
{
return $this->stderr;
}
/**
* {@inheritdoc}
*/
public function setErrorOutput(OutputInterface $error)
{
$this->stderr = $error;
}
/**
* Returns true if current environment supports writing console output to
* STDOUT.
*
* @return bool
*/
protected function hasStdoutSupport()
{
return false === $this->isRunningOS400();
}
/**
* Returns true if current environment supports writing console output to
* STDERR.
*
* @return bool
*/
protected function hasStderrSupport()
{
return false === $this->isRunningOS400();
}
/**
* Checks if current executing environment is IBM iSeries (OS400), which
* doesn't properly convert character-encodings between ASCII to EBCDIC.
*/
private function isRunningOS400(): bool
{
$checks = [
\function_exists('php_uname') ? php_uname('s') : '',
getenv('OSTYPE'),
PHP_OS,
];
return false !== stripos(implode(';', $checks), 'OS400');
}
/**
* @return resource
*/
private function openOutputStream()
{
if (!$this->hasStdoutSupport()) {
return fopen('php://output', 'w');
}
return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
}
/**
* @return resource
*/
private function openErrorStream()
{
return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* NullOutput suppresses all output.
*
* $output = new NullOutput();
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
*/
class NullOutput implements OutputInterface
{
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
// to comply with the interface we must return a OutputFormatterInterface
return new OutputFormatter();
}
/**
* {@inheritdoc}
*/
public function setDecorated($decorated)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return false;
}
/**
* {@inheritdoc}
*/
public function setVerbosity($level)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return self::VERBOSITY_QUIET;
}
/**
* {@inheritdoc}
*/
public function isQuiet()
{
return true;
}
/**
* {@inheritdoc}
*/
public function isVerbose()
{
return false;
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
{
return false;
}
/**
* {@inheritdoc}
*/
public function isDebug()
{
return false;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, $options = self::OUTPUT_NORMAL)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL)
{
// do nothing
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* StreamOutput writes the output to a given stream.
*
* Usage:
*
* $output = new StreamOutput(fopen('php://stdout', 'w'));
*
* As `StreamOutput` can use any stream, you can also use a file:
*
* $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class StreamOutput extends Output
{
private $stream;
/**
* @param resource $stream A stream resource
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*
* @throws InvalidArgumentException When first argument is not a real stream
*/
public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null)
{
if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
}
$this->stream = $stream;
if (null === $decorated) {
$decorated = $this->hasColorSupport();
}
parent::__construct($verbosity, $decorated, $formatter);
}
/**
* Gets the stream attached to this StreamOutput instance.
*
* @return resource A stream resource
*/
public function getStream()
{
return $this->stream;
}
/**
* {@inheritdoc}
*/
protected function doWrite($message, $newline)
{
if ($newline) {
$message .= PHP_EOL;
}
if (false === @fwrite($this->stream, $message)) {
// should never happen
throw new RuntimeException('Unable to write output.');
}
fflush($this->stream);
}
/**
* Returns true if the stream supports colorization.
*
* Colorization is disabled if not supported by the stream:
*
* This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
* terminals via named pipes, so we can only check the environment.
*
* Reference: Composer\XdebugHandler\Process::supportsColor
* https://github.com/composer/xdebug-handler
*
* @return bool true if the stream supports colorization, false otherwise
*/
protected function hasColorSupport()
{
// Follow https://no-color.org/
if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {
return false;
}
if ('Hyper' === getenv('TERM_PROGRAM')) {
return true;
}
if (\DIRECTORY_SEPARATOR === '\\') {
return (\function_exists('sapi_windows_vt100_support')
&& @sapi_windows_vt100_support($this->stream))
|| false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM');
}
if (\function_exists('stream_isatty')) {
return @stream_isatty($this->stream);
}
if (\function_exists('posix_isatty')) {
return @posix_isatty($this->stream);
}
$stat = @fstat($this->stream);
// Check if formatted mode is S_IFCHR
return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Style;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Decorates output to add console style guide helpers.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
abstract class OutputStyle implements OutputInterface, StyleInterface
{
private $output;
public function __construct(OutputInterface $output)
{
$this->output = $output;
}
/**
* {@inheritdoc}
*/
public function newLine($count = 1)
{
$this->output->write(str_repeat(PHP_EOL, $count));
}
/**
* @param int $max
*
* @return ProgressBar
*/
public function createProgressBar($max = 0)
{
return new ProgressBar($this->output, $max);
}
/**
* {@inheritdoc}
*/
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
$this->output->write($messages, $newline, $type);
}
/**
* {@inheritdoc}
*/
public function writeln($messages, $type = self::OUTPUT_NORMAL)
{
$this->output->writeln($messages, $type);
}
/**
* {@inheritdoc}
*/
public function setVerbosity($level)
{
$this->output->setVerbosity($level);
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return $this->output->getVerbosity();
}
/**
* {@inheritdoc}
*/
public function setDecorated($decorated)
{
$this->output->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return $this->output->isDecorated();
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
$this->output->setFormatter($formatter);
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->output->getFormatter();
}
/**
* {@inheritdoc}
*/
public function isQuiet()
{
return $this->output->isQuiet();
}
/**
* {@inheritdoc}
*/
public function isVerbose()
{
return $this->output->isVerbose();
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
{
return $this->output->isVeryVerbose();
}
/**
* {@inheritdoc}
*/
public function isDebug()
{
return $this->output->isDebug();
}
protected function getErrorOutput()
{
if (!$this->output instanceof ConsoleOutputInterface) {
return $this->output;
}
return $this->output->getErrorOutput();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Style;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;
/**
* Output decorator helpers for the Symfony Style Guide.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class SymfonyStyle extends OutputStyle
{
const MAX_LINE_LENGTH = 120;
private $input;
private $questionHelper;
private $progressBar;
private $lineLength;
private $bufferedOutput;
public function __construct(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter());
// Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
$width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH;
$this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);
parent::__construct($output);
}
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
* @param string|null $type The block type (added in [] on first line)
* @param string|null $style The style to apply to the whole block
* @param string $prefix The prefix for the block
* @param bool $padding Whether to add vertical padding
* @param bool $escape Whether to escape the message
*/
public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = true)
{
$messages = \is_array($messages) ? array_values($messages) : [$messages];
$this->autoPrependBlock();
$this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape));
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function title($message)
{
$this->autoPrependBlock();
$this->writeln([
sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
sprintf('<comment>%s</>', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
]);
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function section($message)
{
$this->autoPrependBlock();
$this->writeln([
sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
sprintf('<comment>%s</>', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
]);
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function listing(array $elements)
{
$this->autoPrependText();
$elements = array_map(function ($element) {
return sprintf(' * %s', $element);
}, $elements);
$this->writeln($elements);
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function text($message)
{
$this->autoPrependText();
$messages = \is_array($message) ? array_values($message) : [$message];
foreach ($messages as $message) {
$this->writeln(sprintf(' %s', $message));
}
}
/**
* Formats a command comment.
*
* @param string|array $message
*/
public function comment($message)
{
$this->block($message, null, null, '<fg=default;bg=default> // </>', false, false);
}
/**
* {@inheritdoc}
*/
public function success($message)
{
$this->block($message, 'OK', 'fg=black;bg=green', ' ', true);
}
/**
* {@inheritdoc}
*/
public function error($message)
{
$this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true);
}
/**
* {@inheritdoc}
*/
public function warning($message)
{
$this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true);
}
/**
* {@inheritdoc}
*/
public function note($message)
{
$this->block($message, 'NOTE', 'fg=yellow', ' ! ');
}
/**
* {@inheritdoc}
*/
public function caution($message)
{
$this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true);
}
/**
* {@inheritdoc}
*/
public function table(array $headers, array $rows)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$table->setHeaders($headers);
$table->setRows($rows);
$table->setStyle($style);
$table->render();
$this->newLine();
}
/**
* Formats a horizontal table.
*/
public function horizontalTable(array $headers, array $rows)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$table->setHeaders($headers);
$table->setRows($rows);
$table->setStyle($style);
$table->setHorizontal(true);
$table->render();
$this->newLine();
}
/**
* Formats a list of key/value horizontally.
*
* Each row can be one of:
* * 'A title'
* * ['key' => 'value']
* * new TableSeparator()
*
* @param string|array|TableSeparator ...$list
*/
public function definitionList(...$list)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$headers = [];
$row = [];
foreach ($list as $value) {
if ($value instanceof TableSeparator) {
$headers[] = $value;
$row[] = $value;
continue;
}
if (\is_string($value)) {
$headers[] = new TableCell($value, ['colspan' => 2]);
$row[] = null;
continue;
}
if (!\is_array($value)) {
throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.');
}
$headers[] = key($value);
$row[] = current($value);
}
$table->setHeaders($headers);
$table->setRows([$row]);
$table->setHorizontal();
$table->setStyle($style);
$table->render();
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function ask($question, $default = null, $validator = null)
{
$question = new Question($question, $default);
$question->setValidator($validator);
return $this->askQuestion($question);
}
/**
* {@inheritdoc}
*/
public function askHidden($question, $validator = null)
{
$question = new Question($question);
$question->setHidden(true);
$question->setValidator($validator);
return $this->askQuestion($question);
}
/**
* {@inheritdoc}
*/
public function confirm($question, $default = true)
{
return $this->askQuestion(new ConfirmationQuestion($question, $default));
}
/**
* {@inheritdoc}
*/
public function choice($question, array $choices, $default = null)
{
if (null !== $default) {
$values = array_flip($choices);
$default = $values[$default];
}
return $this->askQuestion(new ChoiceQuestion($question, $choices, $default));
}
/**
* {@inheritdoc}
*/
public function progressStart($max = 0)
{
$this->progressBar = $this->createProgressBar($max);
$this->progressBar->start();
}
/**
* {@inheritdoc}
*/
public function progressAdvance($step = 1)
{
$this->getProgressBar()->advance($step);
}
/**
* {@inheritdoc}
*/
public function progressFinish()
{
$this->getProgressBar()->finish();
$this->newLine(2);
$this->progressBar = null;
}
/**
* {@inheritdoc}
*/
public function createProgressBar($max = 0)
{
$progressBar = parent::createProgressBar($max);
if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) {
$progressBar->setEmptyBarCharacter('â–‘'); // light shade character \u2591
$progressBar->setProgressCharacter('');
$progressBar->setBarCharacter('â–“'); // dark shade character \u2593
}
return $progressBar;
}
/**
* @return mixed
*/
public function askQuestion(Question $question)
{
if ($this->input->isInteractive()) {
$this->autoPrependBlock();
}
if (!$this->questionHelper) {
$this->questionHelper = new SymfonyQuestionHelper();
}
$answer = $this->questionHelper->ask($this->input, $this, $question);
if ($this->input->isInteractive()) {
$this->newLine();
$this->bufferedOutput->write("\n");
}
return $answer;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, $type = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
}
foreach ($messages as $message) {
parent::writeln($message, $type);
$this->writeBuffer($message, true, $type);
}
}
/**
* {@inheritdoc}
*/
public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
}
foreach ($messages as $message) {
parent::write($message, $newline, $type);
$this->writeBuffer($message, $newline, $type);
}
}
/**
* {@inheritdoc}
*/
public function newLine($count = 1)
{
parent::newLine($count);
$this->bufferedOutput->write(str_repeat("\n", $count));
}
/**
* Returns a new instance which makes use of stderr if available.
*
* @return self
*/
public function getErrorStyle()
{
return new self($this->input, $this->getErrorOutput());
}
private function getProgressBar(): ProgressBar
{
if (!$this->progressBar) {
throw new RuntimeException('The ProgressBar is not started.');
}
return $this->progressBar;
}
private function autoPrependBlock(): void
{
$chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);
if (!isset($chars[0])) {
$this->newLine(); //empty history, so we should start with a new line.
return;
}
//Prepend new line for each non LF chars (This means no blank line was output before)
$this->newLine(2 - substr_count($chars, "\n"));
}
private function autoPrependText(): void
{
$fetched = $this->bufferedOutput->fetch();
//Prepend new line if last char isn't EOL:
if ("\n" !== substr($fetched, -1)) {
$this->newLine();
}
}
private function writeBuffer(string $message, bool $newLine, int $type): void
{
// We need to know if the two last chars are PHP_EOL
// Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer
$this->bufferedOutput->write(substr($message, -4), $newLine, $type);
}
private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array
{
$indentLength = 0;
$prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix);
$lines = [];
if (null !== $type) {
$type = sprintf('[%s] ', $type);
$indentLength = \strlen($type);
$lineIndentation = str_repeat(' ', $indentLength);
}
// wrap and add newlines for each element
foreach ($messages as $key => $message) {
if ($escape) {
$message = OutputFormatter::escape($message);
}
$lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true)));
if (\count($messages) > 1 && $key < \count($messages) - 1) {
$lines[] = '';
}
}
$firstLineIndex = 0;
if ($padding && $this->isDecorated()) {
$firstLineIndex = 1;
array_unshift($lines, '');
$lines[] = '';
}
foreach ($lines as $i => &$line) {
if (null !== $type) {
$line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line;
}
$line = $prefix.$line;
$line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line));
if ($style) {
$line = sprintf('<%s>%s</>', $style, $line);
}
}
return $lines;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Style;
/**
* Output style helpers.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
interface StyleInterface
{
/**
* Formats a command title.
*
* @param string $message
*/
public function title($message);
/**
* Formats a section title.
*
* @param string $message
*/
public function section($message);
/**
* Formats a list.
*/
public function listing(array $elements);
/**
* Formats informational text.
*
* @param string|array $message
*/
public function text($message);
/**
* Formats a success result bar.
*
* @param string|array $message
*/
public function success($message);
/**
* Formats an error result bar.
*
* @param string|array $message
*/
public function error($message);
/**
* Formats an warning result bar.
*
* @param string|array $message
*/
public function warning($message);
/**
* Formats a note admonition.
*
* @param string|array $message
*/
public function note($message);
/**
* Formats a caution admonition.
*
* @param string|array $message
*/
public function caution($message);
/**
* Formats a table.
*/
public function table(array $headers, array $rows);
/**
* Asks a question.
*
* @param string $question
* @param string|null $default
* @param callable|null $validator
*
* @return mixed
*/
public function ask($question, $default = null, $validator = null);
/**
* Asks a question with the user input hidden.
*
* @param string $question
* @param callable|null $validator
*
* @return mixed
*/
public function askHidden($question, $validator = null);
/**
* Asks for confirmation.
*
* @param string $question
* @param bool $default
*
* @return bool
*/
public function confirm($question, $default = true);
/**
* Asks a choice question.
*
* @param string $question
* @param string|int|null $default
*
* @return mixed
*/
public function choice($question, array $choices, $default = null);
/**
* Add newline(s).
*
* @param int $count The number of newlines
*/
public function newLine($count = 1);
/**
* Starts the progress output.
*
* @param int $max Maximum steps (0 if unknown)
*/
public function progressStart($max = 0);
/**
* Advances the progress output X steps.
*
* @param int $step Number of steps to advance
*/
public function progressAdvance($step = 1);
/**
* Finishes the progress output.
*/
public function progressFinish();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Question;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* Represents a choice question.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ChoiceQuestion extends Question
{
private $choices;
private $multiselect = false;
private $prompt = ' > ';
private $errorMessage = 'Value "%s" is invalid';
/**
* @param string $question The question to ask to the user
* @param array $choices The list of available choices
* @param mixed $default The default answer to return
*/
public function __construct(string $question, array $choices, $default = null)
{
if (!$choices) {
throw new \LogicException('Choice question must have at least 1 choice available.');
}
parent::__construct($question, $default);
$this->choices = $choices;
$this->setValidator($this->getDefaultValidator());
$this->setAutocompleterValues($choices);
}
/**
* Returns available choices.
*
* @return array
*/
public function getChoices()
{
return $this->choices;
}
/**
* Sets multiselect option.
*
* When multiselect is set to true, multiple choices can be answered.
*
* @param bool $multiselect
*
* @return $this
*/
public function setMultiselect($multiselect)
{
$this->multiselect = $multiselect;
$this->setValidator($this->getDefaultValidator());
return $this;
}
/**
* Returns whether the choices are multiselect.
*
* @return bool
*/
public function isMultiselect()
{
return $this->multiselect;
}
/**
* Gets the prompt for choices.
*
* @return string
*/
public function getPrompt()
{
return $this->prompt;
}
/**
* Sets the prompt for choices.
*
* @param string $prompt
*
* @return $this
*/
public function setPrompt($prompt)
{
$this->prompt = $prompt;
return $this;
}
/**
* Sets the error message for invalid values.
*
* The error message has a string placeholder (%s) for the invalid value.
*
* @param string $errorMessage
*
* @return $this
*/
public function setErrorMessage($errorMessage)
{
$this->errorMessage = $errorMessage;
$this->setValidator($this->getDefaultValidator());
return $this;
}
private function getDefaultValidator(): callable
{
$choices = $this->choices;
$errorMessage = $this->errorMessage;
$multiselect = $this->multiselect;
$isAssoc = $this->isAssoc($choices);
return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
if ($multiselect) {
// Check for a separated comma values
if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selected, $matches)) {
throw new InvalidArgumentException(sprintf($errorMessage, $selected));
}
$selectedChoices = explode(',', $selected);
} else {
$selectedChoices = [$selected];
}
if ($this->isTrimmable()) {
foreach ($selectedChoices as $k => $v) {
$selectedChoices[$k] = trim($v);
}
}
$multiselectChoices = [];
foreach ($selectedChoices as $value) {
$results = [];
foreach ($choices as $key => $choice) {
if ($choice === $value) {
$results[] = $key;
}
}
if (\count($results) > 1) {
throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results)));
}
$result = array_search($value, $choices);
if (!$isAssoc) {
if (false !== $result) {
$result = $choices[$result];
} elseif (isset($choices[$value])) {
$result = $choices[$value];
}
} elseif (false === $result && isset($choices[$value])) {
$result = $value;
}
if (false === $result) {
throw new InvalidArgumentException(sprintf($errorMessage, $value));
}
$multiselectChoices[] = (string) $result;
}
if ($multiselect) {
return $multiselectChoices;
}
return current($multiselectChoices);
};
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Question;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Represents a Question.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Question
{
private $question;
private $attempts;
private $hidden = false;
private $hiddenFallback = true;
private $autocompleterCallback;
private $validator;
private $default;
private $normalizer;
private $trimmable = true;
/**
* @param string $question The question to ask to the user
* @param mixed $default The default answer to return if the user enters nothing
*/
public function __construct(string $question, $default = null)
{
$this->question = $question;
$this->default = $default;
}
/**
* Returns the question.
*
* @return string
*/
public function getQuestion()
{
return $this->question;
}
/**
* Returns the default answer.
*
* @return mixed
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns whether the user response must be hidden.
*
* @return bool
*/
public function isHidden()
{
return $this->hidden;
}
/**
* Sets whether the user response must be hidden or not.
*
* @param bool $hidden
*
* @return $this
*
* @throws LogicException In case the autocompleter is also used
*/
public function setHidden($hidden)
{
if ($this->autocompleterCallback) {
throw new LogicException('A hidden question cannot use the autocompleter.');
}
$this->hidden = (bool) $hidden;
return $this;
}
/**
* In case the response can not be hidden, whether to fallback on non-hidden question or not.
*
* @return bool
*/
public function isHiddenFallback()
{
return $this->hiddenFallback;
}
/**
* Sets whether to fallback on non-hidden question if the response can not be hidden.
*
* @param bool $fallback
*
* @return $this
*/
public function setHiddenFallback($fallback)
{
$this->hiddenFallback = (bool) $fallback;
return $this;
}
/**
* Gets values for the autocompleter.
*
* @return iterable|null
*/
public function getAutocompleterValues()
{
$callback = $this->getAutocompleterCallback();
return $callback ? $callback('') : null;
}
/**
* Sets values for the autocompleter.
*
* @param iterable|null $values
*
* @return $this
*
* @throws InvalidArgumentException
* @throws LogicException
*/
public function setAutocompleterValues($values)
{
if (\is_array($values)) {
$values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values);
$callback = static function () use ($values) {
return $values;
};
} elseif ($values instanceof \Traversable) {
$valueCache = null;
$callback = static function () use ($values, &$valueCache) {
return $valueCache ?? $valueCache = iterator_to_array($values, false);
};
} elseif (null === $values) {
$callback = null;
} else {
throw new InvalidArgumentException('Autocompleter values can be either an array, "null" or a "Traversable" object.');
}
return $this->setAutocompleterCallback($callback);
}
/**
* Gets the callback function used for the autocompleter.
*/
public function getAutocompleterCallback(): ?callable
{
return $this->autocompleterCallback;
}
/**
* Sets the callback function used for the autocompleter.
*
* The callback is passed the user input as argument and should return an iterable of corresponding suggestions.
*
* @return $this
*/
public function setAutocompleterCallback(callable $callback = null): self
{
if ($this->hidden && null !== $callback) {
throw new LogicException('A hidden question cannot use the autocompleter.');
}
$this->autocompleterCallback = $callback;
return $this;
}
/**
* Sets a validator for the question.
*
* @return $this
*/
public function setValidator(callable $validator = null)
{
$this->validator = $validator;
return $this;
}
/**
* Gets the validator for the question.
*
* @return callable|null
*/
public function getValidator()
{
return $this->validator;
}
/**
* Sets the maximum number of attempts.
*
* Null means an unlimited number of attempts.
*
* @param int|null $attempts
*
* @return $this
*
* @throws InvalidArgumentException in case the number of attempts is invalid
*/
public function setMaxAttempts($attempts)
{
if (null !== $attempts && $attempts < 1) {
throw new InvalidArgumentException('Maximum number of attempts must be a positive value.');
}
$this->attempts = $attempts;
return $this;
}
/**
* Gets the maximum number of attempts.
*
* Null means an unlimited number of attempts.
*
* @return int|null
*/
public function getMaxAttempts()
{
return $this->attempts;
}
/**
* Sets a normalizer for the response.
*
* The normalizer can be a callable (a string), a closure or a class implementing __invoke.
*
* @return $this
*/
public function setNormalizer(callable $normalizer)
{
$this->normalizer = $normalizer;
return $this;
}
/**
* Gets the normalizer for the response.
*
* The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
*
* @return callable|null
*/
public function getNormalizer()
{
return $this->normalizer;
}
protected function isAssoc($array)
{
return (bool) \count(array_filter(array_keys($array), 'is_string'));
}
public function isTrimmable(): bool
{
return $this->trimmable;
}
/**
* @return $this
*/
public function setTrimmable(bool $trimmable): self
{
$this->trimmable = $trimmable;
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Question;
/**
* Represents a yes/no question.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConfirmationQuestion extends Question
{
private $trueAnswerRegex;
/**
* @param string $question The question to ask to the user
* @param bool $default The default answer to return, true or false
* @param string $trueAnswerRegex A regex to match the "yes" answer
*/
public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i')
{
parent::__construct($question, $default);
$this->trueAnswerRegex = $trueAnswerRegex;
$this->setNormalizer($this->getDefaultNormalizer());
}
/**
* Returns the default answer normalizer.
*/
private function getDefaultNormalizer(): callable
{
$default = $this->getDefault();
$regex = $this->trueAnswerRegex;
return function ($answer) use ($default, $regex) {
if (\is_bool($answer)) {
return $answer;
}
$answerIsTrue = (bool) preg_match($regex, $answer);
if (false === $default) {
return $answer && $answerIsTrue;
}
return '' === $answer || $answerIsTrue;
};
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* Formatter style class for defining styles.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
private static $availableForegroundColors = [
'black' => ['set' => 30, 'unset' => 39],
'red' => ['set' => 31, 'unset' => 39],
'green' => ['set' => 32, 'unset' => 39],
'yellow' => ['set' => 33, 'unset' => 39],
'blue' => ['set' => 34, 'unset' => 39],
'magenta' => ['set' => 35, 'unset' => 39],
'cyan' => ['set' => 36, 'unset' => 39],
'white' => ['set' => 37, 'unset' => 39],
'default' => ['set' => 39, 'unset' => 39],
];
private static $availableBackgroundColors = [
'black' => ['set' => 40, 'unset' => 49],
'red' => ['set' => 41, 'unset' => 49],
'green' => ['set' => 42, 'unset' => 49],
'yellow' => ['set' => 43, 'unset' => 49],
'blue' => ['set' => 44, 'unset' => 49],
'magenta' => ['set' => 45, 'unset' => 49],
'cyan' => ['set' => 46, 'unset' => 49],
'white' => ['set' => 47, 'unset' => 49],
'default' => ['set' => 49, 'unset' => 49],
];
private static $availableOptions = [
'bold' => ['set' => 1, 'unset' => 22],
'underscore' => ['set' => 4, 'unset' => 24],
'blink' => ['set' => 5, 'unset' => 25],
'reverse' => ['set' => 7, 'unset' => 27],
'conceal' => ['set' => 8, 'unset' => 28],
];
private $foreground;
private $background;
private $href;
private $options = [];
private $handlesHrefGracefully;
/**
* Initializes output formatter style.
*
* @param string|null $foreground The style foreground color name
* @param string|null $background The style background color name
*/
public function __construct(string $foreground = null, string $background = null, array $options = [])
{
if (null !== $foreground) {
$this->setForeground($foreground);
}
if (null !== $background) {
$this->setBackground($background);
}
if (\count($options)) {
$this->setOptions($options);
}
}
/**
* {@inheritdoc}
*/
public function setForeground($color = null)
{
if (null === $color) {
$this->foreground = null;
return;
}
if (!isset(static::$availableForegroundColors[$color])) {
throw new InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors))));
}
$this->foreground = static::$availableForegroundColors[$color];
}
/**
* {@inheritdoc}
*/
public function setBackground($color = null)
{
if (null === $color) {
$this->background = null;
return;
}
if (!isset(static::$availableBackgroundColors[$color])) {
throw new InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors))));
}
$this->background = static::$availableBackgroundColors[$color];
}
public function setHref(string $url): void
{
$this->href = $url;
}
/**
* {@inheritdoc}
*/
public function setOption($option)
{
if (!isset(static::$availableOptions[$option])) {
throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
}
if (!\in_array(static::$availableOptions[$option], $this->options)) {
$this->options[] = static::$availableOptions[$option];
}
}
/**
* {@inheritdoc}
*/
public function unsetOption($option)
{
if (!isset(static::$availableOptions[$option])) {
throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
}
$pos = array_search(static::$availableOptions[$option], $this->options);
if (false !== $pos) {
unset($this->options[$pos]);
}
}
/**
* {@inheritdoc}
*/
public function setOptions(array $options)
{
$this->options = [];
foreach ($options as $option) {
$this->setOption($option);
}
}
/**
* {@inheritdoc}
*/
public function apply($text)
{
$setCodes = [];
$unsetCodes = [];
if (null === $this->handlesHrefGracefully) {
$this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') && !getenv('KONSOLE_VERSION');
}
if (null !== $this->foreground) {
$setCodes[] = $this->foreground['set'];
$unsetCodes[] = $this->foreground['unset'];
}
if (null !== $this->background) {
$setCodes[] = $this->background['set'];
$unsetCodes[] = $this->background['unset'];
}
foreach ($this->options as $option) {
$setCodes[] = $option['set'];
$unsetCodes[] = $option['unset'];
}
if (null !== $this->href && $this->handlesHrefGracefully) {
$text = "\033]8;;$this->href\033\\$text\033]8;;\033\\";
}
if (0 === \count($setCodes)) {
return $text;
}
return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* Formatter style interface for defining styles.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface OutputFormatterStyleInterface
{
/**
* Sets style foreground color.
*
* @param string|null $color The color name
*/
public function setForeground($color = null);
/**
* Sets style background color.
*
* @param string $color The color name
*/
public function setBackground($color = null);
/**
* Sets some specific style option.
*
* @param string $option The option name
*/
public function setOption($option);
/**
* Unsets some specific style option.
*
* @param string $option The option name
*/
public function unsetOption($option);
/**
* Sets multiple style options at once.
*/
public function setOptions(array $options);
/**
* Applies the style to a given text.
*
* @param string $text The text to style
*
* @return string
*/
public function apply($text);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* Formatter interface for console output.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface OutputFormatterInterface
{
/**
* Sets the decorated flag.
*
* @param bool $decorated Whether to decorate the messages or not
*/
public function setDecorated($decorated);
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
*/
public function isDecorated();
/**
* Sets a new style.
*
* @param string $name The style name
*/
public function setStyle($name, OutputFormatterStyleInterface $style);
/**
* Checks if output formatter has style with specified name.
*
* @param string $name
*
* @return bool
*/
public function hasStyle($name);
/**
* Gets style options from style with specified name.
*
* @param string $name
*
* @return OutputFormatterStyleInterface
*
* @throws \InvalidArgumentException When style isn't defined
*/
public function getStyle($name);
/**
* Formats a message according to the given styles.
*
* @param string $message The message to style
*
* @return string The styled message
*/
public function format($message);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* Formatter class for console output.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
* @author Roland Franssen <franssen.roland@gmail.com>
*/
class OutputFormatter implements WrappableOutputFormatterInterface
{
private $decorated;
private $styles = [];
private $styleStack;
/**
* Escapes "<" special char in given text.
*
* @param string $text Text to escape
*
* @return string Escaped text
*/
public static function escape($text)
{
$text = preg_replace('/([^\\\\]?)</', '$1\\<', $text);
return self::escapeTrailingBackslash($text);
}
/**
* Escapes trailing "\" in given text.
*
* @internal
*/
public static function escapeTrailingBackslash(string $text): string
{
if ('\\' === substr($text, -1)) {
$len = \strlen($text);
$text = rtrim($text, '\\');
$text = str_replace("\0", '', $text);
$text .= str_repeat("\0", $len - \strlen($text));
}
return $text;
}
/**
* Initializes console output formatter.
*
* @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
*/
public function __construct(bool $decorated = false, array $styles = [])
{
$this->decorated = $decorated;
$this->setStyle('error', new OutputFormatterStyle('white', 'red'));
$this->setStyle('info', new OutputFormatterStyle('green'));
$this->setStyle('comment', new OutputFormatterStyle('yellow'));
$this->setStyle('question', new OutputFormatterStyle('black', 'cyan'));
foreach ($styles as $name => $style) {
$this->setStyle($name, $style);
}
$this->styleStack = new OutputFormatterStyleStack();
}
/**
* {@inheritdoc}
*/
public function setDecorated($decorated)
{
$this->decorated = (bool) $decorated;
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return $this->decorated;
}
/**
* {@inheritdoc}
*/
public function setStyle($name, OutputFormatterStyleInterface $style)
{
$this->styles[strtolower($name)] = $style;
}
/**
* {@inheritdoc}
*/
public function hasStyle($name)
{
return isset($this->styles[strtolower($name)]);
}
/**
* {@inheritdoc}
*/
public function getStyle($name)
{
if (!$this->hasStyle($name)) {
throw new InvalidArgumentException(sprintf('Undefined style: %s', $name));
}
return $this->styles[strtolower($name)];
}
/**
* {@inheritdoc}
*/
public function format($message)
{
return $this->formatAndWrap((string) $message, 0);
}
/**
* {@inheritdoc}
*/
public function formatAndWrap(string $message, int $width)
{
$offset = 0;
$output = '';
$tagRegex = '[a-z][^<>]*+';
$currentLineLength = 0;
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE);
foreach ($matches[0] as $i => $match) {
$pos = $match[1];
$text = $match[0];
if (0 != $pos && '\\' == $message[$pos - 1]) {
continue;
}
// add the text up to the next tag
$output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
$offset = $pos + \strlen($text);
// opening tag?
if ($open = '/' != $text[1]) {
$tag = $matches[1][$i][0];
} else {
$tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : '';
}
if (!$open && !$tag) {
// </>
$this->styleStack->pop();
} elseif (null === $style = $this->createStyleFromString($tag)) {
$output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength);
} elseif ($open) {
$this->styleStack->push($style);
} else {
$this->styleStack->pop($style);
}
}
$output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength);
if (false !== strpos($output, "\0")) {
return strtr($output, ["\0" => '\\', '\\<' => '<']);
}
return str_replace('\\<', '<', $output);
}
/**
* @return OutputFormatterStyleStack
*/
public function getStyleStack()
{
return $this->styleStack;
}
/**
* Tries to create new style instance from string.
*/
private function createStyleFromString(string $string): ?OutputFormatterStyleInterface
{
if (isset($this->styles[$string])) {
return $this->styles[$string];
}
if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, PREG_SET_ORDER)) {
return null;
}
$style = new OutputFormatterStyle();
foreach ($matches as $match) {
array_shift($match);
$match[0] = strtolower($match[0]);
if ('fg' == $match[0]) {
$style->setForeground(strtolower($match[1]));
} elseif ('bg' == $match[0]) {
$style->setBackground(strtolower($match[1]));
} elseif ('href' === $match[0]) {
$style->setHref($match[1]);
} elseif ('options' === $match[0]) {
preg_match_all('([^,;]+)', strtolower($match[1]), $options);
$options = array_shift($options);
foreach ($options as $option) {
$style->setOption($option);
}
} else {
return null;
}
}
return $style;
}
/**
* Applies current style from stack to text, if must be applied.
*/
private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string
{
if ('' === $text) {
return '';
}
if (!$width) {
return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text;
}
if (!$currentLineLength && '' !== $current) {
$text = ltrim($text);
}
if ($currentLineLength) {
$prefix = substr($text, 0, $i = $width - $currentLineLength)."\n";
$text = substr($text, $i);
} else {
$prefix = '';
}
preg_match('~(\\n)$~', $text, $matches);
$text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text);
$text = rtrim($text, "\n").($matches[1] ?? '');
if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) {
$text = "\n".$text;
}
$lines = explode("\n", $text);
foreach ($lines as $line) {
$currentLineLength += \strlen($line);
if ($width <= $currentLineLength) {
$currentLineLength = 0;
}
}
if ($this->isDecorated()) {
foreach ($lines as $i => $line) {
$lines[$i] = $this->styleStack->getCurrent()->apply($line);
}
}
return implode("\n", $lines);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* Formatter interface for console output that supports word wrapping.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*/
interface WrappableOutputFormatterInterface extends OutputFormatterInterface
{
/**
* Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping).
*/
public function formatAndWrap(string $message, int $width);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Contracts\Service\ResetInterface;
/**
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class OutputFormatterStyleStack implements ResetInterface
{
/**
* @var OutputFormatterStyleInterface[]
*/
private $styles;
private $emptyStyle;
public function __construct(OutputFormatterStyleInterface $emptyStyle = null)
{
$this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle();
$this->reset();
}
/**
* Resets stack (ie. empty internal arrays).
*/
public function reset()
{
$this->styles = [];
}
/**
* Pushes a style in the stack.
*/
public function push(OutputFormatterStyleInterface $style)
{
$this->styles[] = $style;
}
/**
* Pops a style from the stack.
*
* @return OutputFormatterStyleInterface
*
* @throws InvalidArgumentException When style tags incorrectly nested
*/
public function pop(OutputFormatterStyleInterface $style = null)
{
if (empty($this->styles)) {
return $this->emptyStyle;
}
if (null === $style) {
return array_pop($this->styles);
}
foreach (array_reverse($this->styles, true) as $index => $stackedStyle) {
if ($style->apply('') === $stackedStyle->apply('')) {
$this->styles = \array_slice($this->styles, 0, $index);
return $stackedStyle;
}
}
throw new InvalidArgumentException('Incorrectly nested style tag found.');
}
/**
* Computes current style with stacks top codes.
*
* @return OutputFormatterStyle
*/
public function getCurrent()
{
if (empty($this->styles)) {
return $this->emptyStyle;
}
return $this->styles[\count($this->styles) - 1];
}
/**
* @return $this
*/
public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle)
{
$this->emptyStyle = $emptyStyle;
return $this;
}
/**
* @return OutputFormatterStyleInterface
*/
public function getEmptyStyle()
{
return $this->emptyStyle;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
*/
interface CommandLoaderInterface
{
/**
* Loads a command.
*
* @param string $name
*
* @return Command
*
* @throws CommandNotFoundException
*/
public function get($name);
/**
* Checks if a command exists.
*
* @param string $name
*
* @return bool
*/
public function has($name);
/**
* @return string[] All registered command names
*/
public function getNames();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* Loads commands from a PSR-11 container.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class ContainerCommandLoader implements CommandLoaderInterface
{
private $container;
private $commandMap;
/**
* @param array $commandMap An array with command names as keys and service ids as values
*/
public function __construct(ContainerInterface $container, array $commandMap)
{
$this->container = $container;
$this->commandMap = $commandMap;
}
/**
* {@inheritdoc}
*/
public function get($name)
{
if (!$this->has($name)) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
return $this->container->get($this->commandMap[$name]);
}
/**
* {@inheritdoc}
*/
public function has($name)
{
return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
}
/**
* {@inheritdoc}
*/
public function getNames()
{
return array_keys($this->commandMap);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* A simple command loader using factories to instantiate commands lazily.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class FactoryCommandLoader implements CommandLoaderInterface
{
private $factories;
/**
* @param callable[] $factories Indexed by command names
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}
/**
* {@inheritdoc}
*/
public function has($name)
{
return isset($this->factories[$name]);
}
/**
* {@inheritdoc}
*/
public function get($name)
{
if (!isset($this->factories[$name])) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
$factory = $this->factories[$name];
return $factory();
}
/**
* {@inheritdoc}
*/
public function getNames()
{
return array_keys($this->factories);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* Text descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class TextDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
} else {
$default = '';
}
$totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($argument->getName());
$spacingWidth = $totalWidth - \strlen($argument->getName());
$this->writeText(sprintf(' <info>%s</info> %s%s%s',
$argument->getName(),
str_repeat(' ', $spacingWidth),
// + 4 = 2 spaces before <info>, 2 spaces after </info>
preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
$default
), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
} else {
$default = '';
}
$value = '';
if ($option->acceptValue()) {
$value = '='.strtoupper($option->getName());
if ($option->isValueOptional()) {
$value = '['.$value.']';
}
}
$totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]);
$synopsis = sprintf('%s%s',
$option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ',
sprintf('--%s%s', $option->getName(), $value)
);
$spacingWidth = $totalWidth - Helper::strlen($synopsis);
$this->writeText(sprintf(' <info>%s</info> %s%s%s%s',
$synopsis,
str_repeat(' ', $spacingWidth),
// + 4 = 2 spaces before <info>, 2 spaces after </info>
preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
$default,
$option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''
), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
foreach ($definition->getArguments() as $argument) {
$totalWidth = max($totalWidth, Helper::strlen($argument->getName()));
}
if ($definition->getArguments()) {
$this->writeText('<comment>Arguments:</comment>', $options);
$this->writeText("\n");
foreach ($definition->getArguments() as $argument) {
$this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
$this->writeText("\n");
}
}
if ($definition->getArguments() && $definition->getOptions()) {
$this->writeText("\n");
}
if ($definition->getOptions()) {
$laterOptions = [];
$this->writeText('<comment>Options:</comment>', $options);
foreach ($definition->getOptions() as $option) {
if (\strlen($option->getShortcut()) > 1) {
$laterOptions[] = $option;
continue;
}
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
foreach ($laterOptions as $option) {
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
}
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$command->getSynopsis(true);
$command->getSynopsis(false);
$command->mergeApplicationDefinition(false);
if ($description = $command->getDescription()) {
$this->writeText('<comment>Description:</comment>', $options);
$this->writeText("\n");
$this->writeText(' '.$description);
$this->writeText("\n\n");
}
$this->writeText('<comment>Usage:</comment>', $options);
foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
$this->writeText("\n");
$this->writeText(' '.OutputFormatter::escape($usage), $options);
}
$this->writeText("\n");
$definition = $command->getNativeDefinition();
if ($definition->getOptions() || $definition->getArguments()) {
$this->writeText("\n");
$this->describeInputDefinition($definition, $options);
$this->writeText("\n");
}
$help = $command->getProcessedHelp();
if ($help && $help !== $description) {
$this->writeText("\n");
$this->writeText('<comment>Help:</comment>', $options);
$this->writeText("\n");
$this->writeText(' '.str_replace("\n", "\n ", $help), $options);
$this->writeText("\n");
}
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace);
if (isset($options['raw_text']) && $options['raw_text']) {
$width = $this->getColumnWidth($description->getCommands());
foreach ($description->getCommands() as $command) {
$this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
$this->writeText("\n");
}
} else {
if ('' != $help = $application->getHelp()) {
$this->writeText("$help\n\n", $options);
}
$this->writeText("<comment>Usage:</comment>\n", $options);
$this->writeText(" command [options] [arguments]\n\n", $options);
$this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);
$this->writeText("\n");
$this->writeText("\n");
$commands = $description->getCommands();
$namespaces = $description->getNamespaces();
if ($describedNamespace && $namespaces) {
// make sure all alias commands are included when describing a specific namespace
$describedNamespaceInfo = reset($namespaces);
foreach ($describedNamespaceInfo['commands'] as $name) {
$commands[$name] = $description->getCommand($name);
}
}
// calculate max. width based on available commands per namespace
$width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) {
return array_intersect($namespace['commands'], array_keys($commands));
}, $namespaces))));
if ($describedNamespace) {
$this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
} else {
$this->writeText('<comment>Available commands:</comment>', $options);
}
foreach ($namespaces as $namespace) {
$namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) {
return isset($commands[$name]);
});
if (!$namespace['commands']) {
continue;
}
if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$this->writeText("\n");
$this->writeText(' <comment>'.$namespace['id'].'</comment>', $options);
}
foreach ($namespace['commands'] as $name) {
$this->writeText("\n");
$spacingWidth = $width - Helper::strlen($name);
$command = $commands[$name];
$commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : '';
$this->writeText(sprintf(' <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
}
}
$this->writeText("\n");
}
}
/**
* {@inheritdoc}
*/
private function writeText(string $content, array $options = [])
{
$this->write(
isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
isset($options['raw_output']) ? !$options['raw_output'] : true
);
}
/**
* Formats command aliases to show them in the command description.
*/
private function getCommandAliasesText(Command $command): string
{
$text = '';
$aliases = $command->getAliases();
if ($aliases) {
$text = '['.implode('|', $aliases).'] ';
}
return $text;
}
/**
* Formats input option/argument default value.
*
* @param mixed $default
*/
private function formatDefaultValue($default): string
{
if (INF === $default) {
return 'INF';
}
if (\is_string($default)) {
$default = OutputFormatter::escape($default);
} elseif (\is_array($default)) {
foreach ($default as $key => $value) {
if (\is_string($value)) {
$default[$key] = OutputFormatter::escape($value);
}
}
}
return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
}
/**
* @param (Command|string)[] $commands
*/
private function getColumnWidth(array $commands): int
{
$widths = [];
foreach ($commands as $command) {
if ($command instanceof Command) {
$widths[] = Helper::strlen($command->getName());
foreach ($command->getAliases() as $alias) {
$widths[] = Helper::strlen($alias);
}
} else {
$widths[] = Helper::strlen($command);
}
}
return $widths ? max($widths) + 2 : 0;
}
/**
* @param InputOption[] $options
*/
private function calculateTotalWidthForOptions(array $options): int
{
$totalWidth = 0;
foreach ($options as $option) {
// "-" + shortcut + ", --" + name
$nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName());
if ($option->acceptValue()) {
$valueLength = 1 + Helper::strlen($option->getName()); // = + value
$valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
$nameLength += $valueLength;
}
$totalWidth = max($totalWidth, $nameLength);
}
return $totalWidth;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class ApplicationDescription
{
const GLOBAL_NAMESPACE = '_global';
private $application;
private $namespace;
private $showHidden;
/**
* @var array
*/
private $namespaces;
/**
* @var Command[]
*/
private $commands;
/**
* @var Command[]
*/
private $aliases;
public function __construct(Application $application, string $namespace = null, bool $showHidden = false)
{
$this->application = $application;
$this->namespace = $namespace;
$this->showHidden = $showHidden;
}
public function getNamespaces(): array
{
if (null === $this->namespaces) {
$this->inspectApplication();
}
return $this->namespaces;
}
/**
* @return Command[]
*/
public function getCommands(): array
{
if (null === $this->commands) {
$this->inspectApplication();
}
return $this->commands;
}
/**
* @throws CommandNotFoundException
*/
public function getCommand(string $name): Command
{
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
throw new CommandNotFoundException(sprintf('Command %s does not exist.', $name));
}
return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
}
private function inspectApplication()
{
$this->commands = [];
$this->namespaces = [];
$all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
foreach ($this->sortCommands($all) as $namespace => $commands) {
$names = [];
/** @var Command $command */
foreach ($commands as $name => $command) {
if (!$command->getName() || (!$this->showHidden && $command->isHidden())) {
continue;
}
if ($command->getName() === $name) {
$this->commands[$name] = $command;
} else {
$this->aliases[$name] = $command;
}
$names[] = $name;
}
$this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
}
}
private function sortCommands(array $commands): array
{
$namespacedCommands = [];
$globalCommands = [];
$sortedCommands = [];
foreach ($commands as $name => $command) {
$key = $this->application->extractNamespace($name, 1);
if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) {
$globalCommands[$name] = $command;
} else {
$namespacedCommands[$key][$name] = $command;
}
}
if ($globalCommands) {
ksort($globalCommands);
$sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands;
}
if ($namespacedCommands) {
ksort($namespacedCommands);
foreach ($namespacedCommands as $key => $commandsSet) {
ksort($commandsSet);
$sortedCommands[$key] = $commandsSet;
}
}
return $sortedCommands;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* XML descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class XmlDescriptor extends Descriptor
{
public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($definitionXML = $dom->createElement('definition'));
$definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
foreach ($definition->getArguments() as $argument) {
$this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
}
$definitionXML->appendChild($optionsXML = $dom->createElement('options'));
foreach ($definition->getOptions() as $option) {
$this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
}
return $dom;
}
public function getCommandDocument(Command $command): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($commandXML = $dom->createElement('command'));
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
$commandXML->setAttribute('id', $command->getName());
$commandXML->setAttribute('name', $command->getName());
$commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0);
$commandXML->appendChild($usagesXML = $dom->createElement('usages'));
foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) {
$usagesXML->appendChild($dom->createElement('usage', $usage));
}
$commandXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));
$commandXML->appendChild($helpXML = $dom->createElement('help'));
$helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));
$definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition());
$this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));
return $dom;
}
public function getApplicationDocument(Application $application, string $namespace = null): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($rootXml = $dom->createElement('symfony'));
if ('UNKNOWN' !== $application->getName()) {
$rootXml->setAttribute('name', $application->getName());
if ('UNKNOWN' !== $application->getVersion()) {
$rootXml->setAttribute('version', $application->getVersion());
}
}
$rootXml->appendChild($commandsXML = $dom->createElement('commands'));
$description = new ApplicationDescription($application, $namespace, true);
if ($namespace) {
$commandsXML->setAttribute('namespace', $namespace);
}
foreach ($description->getCommands() as $command) {
$this->appendDocument($commandsXML, $this->getCommandDocument($command));
}
if (!$namespace) {
$rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));
foreach ($description->getNamespaces() as $namespaceDescription) {
$namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
$namespaceArrayXML->setAttribute('id', $namespaceDescription['id']);
foreach ($namespaceDescription['commands'] as $name) {
$namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
$commandXML->appendChild($dom->createTextNode($name));
}
}
}
return $dom;
}
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->writeDocument($this->getInputArgumentDocument($argument));
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$this->writeDocument($this->getInputOptionDocument($option));
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$this->writeDocument($this->getInputDefinitionDocument($definition));
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$this->writeDocument($this->getCommandDocument($command));
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null));
}
/**
* Appends document children to parent node.
*/
private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent)
{
foreach ($importedParent->childNodes as $childNode) {
$parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
}
}
/**
* Writes DOM document.
*/
private function writeDocument(\DOMDocument $dom)
{
$dom->formatOutput = true;
$this->write($dom->saveXML());
}
private function getInputArgumentDocument(InputArgument $argument): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($objectXML = $dom->createElement('argument'));
$objectXML->setAttribute('name', $argument->getName());
$objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
$objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
$defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : []));
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
return $dom;
}
private function getInputOptionDocument(InputOption $option): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($objectXML = $dom->createElement('option'));
$objectXML->setAttribute('name', '--'.$option->getName());
$pos = strpos($option->getShortcut(), '|');
if (false !== $pos) {
$objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos));
$objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut()));
} else {
$objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
}
$objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
$objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
$objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($option->getDescription()));
if ($option->acceptValue()) {
$defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : []));
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
if (!empty($defaults)) {
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
}
}
return $dom;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Descriptor interface.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
interface DescriptorInterface
{
/**
* Describes an object if supported.
*
* @param object $object
*/
public function describe(OutputInterface $output, $object, array $options = []);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
abstract class Descriptor implements DescriptorInterface
{
/**
* @var OutputInterface
*/
protected $output;
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, $object, array $options = [])
{
$this->output = $output;
switch (true) {
case $object instanceof InputArgument:
$this->describeInputArgument($object, $options);
break;
case $object instanceof InputOption:
$this->describeInputOption($object, $options);
break;
case $object instanceof InputDefinition:
$this->describeInputDefinition($object, $options);
break;
case $object instanceof Command:
$this->describeCommand($object, $options);
break;
case $object instanceof Application:
$this->describeApplication($object, $options);
break;
default:
throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', \get_class($object)));
}
}
/**
* Writes content to output.
*
* @param string $content
* @param bool $decorated
*/
protected function write($content, $decorated = false)
{
$this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
}
/**
* Describes an InputArgument instance.
*
* @return string|mixed
*/
abstract protected function describeInputArgument(InputArgument $argument, array $options = []);
/**
* Describes an InputOption instance.
*
* @return string|mixed
*/
abstract protected function describeInputOption(InputOption $option, array $options = []);
/**
* Describes an InputDefinition instance.
*
* @return string|mixed
*/
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []);
/**
* Describes a Command instance.
*
* @return string|mixed
*/
abstract protected function describeCommand(Command $command, array $options = []);
/**
* Describes an Application instance.
*
* @return string|mixed
*/
abstract protected function describeApplication(Application $application, array $options = []);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* JSON descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class JsonDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->writeData($this->getInputArgumentData($argument), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$this->writeData($this->getInputOptionData($option), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$this->writeData($this->getInputDefinitionData($definition), $options);
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$this->writeData($this->getCommandData($command), $options);
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace, true);
$commands = [];
foreach ($description->getCommands() as $command) {
$commands[] = $this->getCommandData($command);
}
$data = [];
if ('UNKNOWN' !== $application->getName()) {
$data['application']['name'] = $application->getName();
if ('UNKNOWN' !== $application->getVersion()) {
$data['application']['version'] = $application->getVersion();
}
}
$data['commands'] = $commands;
if ($describedNamespace) {
$data['namespace'] = $describedNamespace;
} else {
$data['namespaces'] = array_values($description->getNamespaces());
}
$this->writeData($data, $options);
}
/**
* Writes data as json.
*/
private function writeData(array $data, array $options)
{
$flags = isset($options['json_encoding']) ? $options['json_encoding'] : 0;
$this->write(json_encode($data, $flags));
}
private function getInputArgumentData(InputArgument $argument): array
{
return [
'name' => $argument->getName(),
'is_required' => $argument->isRequired(),
'is_array' => $argument->isArray(),
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
'default' => INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
];
}
private function getInputOptionData(InputOption $option): array
{
return [
'name' => '--'.$option->getName(),
'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '',
'accept_value' => $option->acceptValue(),
'is_value_required' => $option->isValueRequired(),
'is_multiple' => $option->isArray(),
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
'default' => INF === $option->getDefault() ? 'INF' : $option->getDefault(),
];
}
private function getInputDefinitionData(InputDefinition $definition): array
{
$inputArguments = [];
foreach ($definition->getArguments() as $name => $argument) {
$inputArguments[$name] = $this->getInputArgumentData($argument);
}
$inputOptions = [];
foreach ($definition->getOptions() as $name => $option) {
$inputOptions[$name] = $this->getInputOptionData($option);
}
return ['arguments' => $inputArguments, 'options' => $inputOptions];
}
private function getCommandData(Command $command): array
{
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
return [
'name' => $command->getName(),
'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()),
'description' => $command->getDescription(),
'help' => $command->getProcessedHelp(),
'definition' => $this->getInputDefinitionData($command->getNativeDefinition()),
'hidden' => $command->isHidden(),
];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Markdown descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class MarkdownDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, $object, array $options = [])
{
$decorated = $output->isDecorated();
$output->setDecorated(false);
parent::describe($output, $object, $options);
$output->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
protected function write($content, $decorated = true)
{
parent::write($content, $decorated);
}
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->write(
'#### `'.($argument->getName() ?: '<none>')."`\n\n"
.($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
.'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
.'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
.'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$name = '--'.$option->getName();
if ($option->getShortcut()) {
$name .= '|-'.str_replace('|', '|-', $option->getShortcut()).'';
}
$this->write(
'#### `'.$name.'`'."\n\n"
.($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '')
.'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
.'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
.'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
.'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
if ($showArguments = \count($definition->getArguments()) > 0) {
$this->write('### Arguments');
foreach ($definition->getArguments() as $argument) {
$this->write("\n\n");
$this->write($this->describeInputArgument($argument));
}
}
if (\count($definition->getOptions()) > 0) {
if ($showArguments) {
$this->write("\n\n");
}
$this->write('### Options');
foreach ($definition->getOptions() as $option) {
$this->write("\n\n");
$this->write($this->describeInputOption($option));
}
}
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$command->getSynopsis();
$command->mergeApplicationDefinition(false);
$this->write(
'`'.$command->getName()."`\n"
.str_repeat('-', Helper::strlen($command->getName()) + 2)."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
.'### Usage'."\n\n"
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) {
return $carry.'* `'.$usage.'`'."\n";
})
);
if ($help = $command->getProcessedHelp()) {
$this->write("\n");
$this->write($help);
}
if ($command->getNativeDefinition()) {
$this->write("\n\n");
$this->describeInputDefinition($command->getNativeDefinition());
}
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
$description = new ApplicationDescription($application, $describedNamespace);
$title = $this->getApplicationTitle($application);
$this->write($title."\n".str_repeat('=', Helper::strlen($title)));
foreach ($description->getNamespaces() as $namespace) {
if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$this->write("\n\n");
$this->write('**'.$namespace['id'].':**');
}
$this->write("\n\n");
$this->write(implode("\n", array_map(function ($commandName) use ($description) {
return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName()));
}, $namespace['commands'])));
}
foreach ($description->getCommands() as $command) {
$this->write("\n\n");
$this->write($this->describeCommand($command));
}
}
private function getApplicationTitle(Application $application): string
{
if ('UNKNOWN' !== $application->getName()) {
if ('UNKNOWN' !== $application->getVersion()) {
return sprintf('%s %s', $application->getName(), $application->getVersion());
}
return $application->getName();
}
return 'Console Tool';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;
/**
* Basic lock feature for commands.
*
* @author Geoffrey Brier <geoffrey.brier@gmail.com>
*/
trait LockableTrait
{
/** @var Lock */
private $lock;
/**
* Locks a command.
*/
private function lock(string $name = null, bool $blocking = false): bool
{
if (!class_exists(SemaphoreStore::class)) {
throw new LogicException('To enable the locking feature you must install the symfony/lock component.');
}
if (null !== $this->lock) {
throw new LogicException('A lock is already in place.');
}
if (SemaphoreStore::isSupported()) {
$store = new SemaphoreStore();
} else {
$store = new FlockStore();
}
$this->lock = (new LockFactory($store))->createLock($name ?: $this->getName());
if (!$this->lock->acquire($blocking)) {
$this->lock = null;
return false;
}
return true;
}
/**
* Releases the command lock if there is one.
*/
private function release()
{
if ($this->lock) {
$this->lock->release();
$this->lock = null;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* HelpCommand displays the help for a given command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HelpCommand extends Command
{
private $command;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->ignoreValidationErrors();
$this
->setName('help')
->setDefinition([
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
])
->setDescription('Displays help for a command')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays help for a given command:
<info>php %command.full_name% list</info>
You can also output the help in other formats by using the <comment>--format</comment> option:
<info>php %command.full_name% --format=xml list</info>
To display the list of available commands, please use the <info>list</info> command.
EOF
)
;
}
public function setCommand(Command $command)
{
$this->command = $command;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (null === $this->command) {
$this->command = $this->getApplication()->find($input->getArgument('command_name'));
}
$helper = new DescriptorHelper();
$helper->describe($output, $this->command, [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
]);
$this->command = null;
return 0;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* ListCommand displays the list of all available commands for the application.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ListCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('list')
->setDefinition($this->createDefinition())
->setDescription('Lists commands')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all commands:
<info>php %command.full_name%</info>
You can also display the commands for a specific namespace:
<info>php %command.full_name% test</info>
You can also output the information in other formats by using the <comment>--format</comment> option:
<info>php %command.full_name% --format=xml</info>
It's also possible to get raw list of commands (useful for embedding command runner):
<info>php %command.full_name% --raw</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
public function getNativeDefinition()
{
return $this->createDefinition();
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$helper = new DescriptorHelper();
$helper->describe($output, $this->getApplication(), [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'namespace' => $input->getArgument('namespace'),
]);
return 0;
}
private function createDefinition(): InputDefinition
{
return new InputDefinition([
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
]);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Base class for all commands.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Command
{
/**
* @var string|null The default command name
*/
protected static $defaultName;
private $application;
private $name;
private $processTitle;
private $aliases = [];
private $definition;
private $hidden = false;
private $help = '';
private $description = '';
private $ignoreValidationErrors = false;
private $applicationDefinitionMerged = false;
private $applicationDefinitionMergedWithArgs = false;
private $code;
private $synopsis = [];
private $usages = [];
private $helperSet;
/**
* @return string|null The default command name or null when no default name is set
*/
public static function getDefaultName()
{
$class = \get_called_class();
$r = new \ReflectionProperty($class, 'defaultName');
return $class === $r->class ? static::$defaultName : null;
}
/**
* @param string|null $name The name of the command; passing null means it must be set in configure()
*
* @throws LogicException When the command name is empty
*/
public function __construct(string $name = null)
{
$this->definition = new InputDefinition();
if (null !== $name || null !== $name = static::getDefaultName()) {
$this->setName($name);
}
$this->configure();
}
/**
* Ignores validation errors.
*
* This is mainly useful for the help command.
*/
public function ignoreValidationErrors()
{
$this->ignoreValidationErrors = true;
}
public function setApplication(Application $application = null)
{
$this->application = $application;
if ($application) {
$this->setHelperSet($application->getHelperSet());
} else {
$this->helperSet = null;
}
}
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
}
/**
* Gets the helper set.
*
* @return HelperSet|null A HelperSet instance
*/
public function getHelperSet()
{
return $this->helperSet;
}
/**
* Gets the application instance for this command.
*
* @return Application|null An Application instance
*/
public function getApplication()
{
return $this->application;
}
/**
* Checks whether the command is enabled or not in the current environment.
*
* Override this to check for x or y and return false if the command can not
* run properly under the current conditions.
*
* @return bool
*/
public function isEnabled()
{
return true;
}
/**
* Configures the current command.
*/
protected function configure()
{
}
/**
* Executes the current command.
*
* This method is not abstract because you can use this class
* as a concrete class. In this case, instead of defining the
* execute() method, you set the code to execute by passing
* a Closure to the setCode() method.
*
* @return int 0 if everything went fine, or an exit code
*
* @throws LogicException When this abstract method is not implemented
*
* @see setCode()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
throw new LogicException('You must override the execute() method in the concrete command class.');
}
/**
* Interacts with the user.
*
* This method is executed before the InputDefinition is validated.
* This means that this is the only place where the command can
* interactively ask for values of missing required arguments.
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
}
/**
* Initializes the command after the input has been bound and before the input
* is validated.
*
* This is mainly useful when a lot of commands extends one main command
* where some things need to be initialized based on the input arguments and options.
*
* @see InputInterface::bind()
* @see InputInterface::validate()
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
}
/**
* Runs the command.
*
* The code to execute is either defined directly with the
* setCode() method or by overriding the execute() method
* in a sub-class.
*
* @return int The command exit code
*
* @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}.
*
* @see setCode()
* @see execute()
*/
public function run(InputInterface $input, OutputInterface $output)
{
// force the creation of the synopsis before the merge with the app definition
$this->getSynopsis(true);
$this->getSynopsis(false);
// add the application arguments and options
$this->mergeApplicationDefinition();
// bind the input against the command specific arguments/options
try {
$input->bind($this->definition);
} catch (ExceptionInterface $e) {
if (!$this->ignoreValidationErrors) {
throw $e;
}
}
$this->initialize($input, $output);
if (null !== $this->processTitle) {
if (\function_exists('cli_set_process_title')) {
if (!@cli_set_process_title($this->processTitle)) {
if ('Darwin' === PHP_OS) {
$output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE);
} else {
cli_set_process_title($this->processTitle);
}
}
} elseif (\function_exists('setproctitle')) {
setproctitle($this->processTitle);
} elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
$output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
}
}
if ($input->isInteractive()) {
$this->interact($input, $output);
}
// The command name argument is often omitted when a command is executed directly with its run() method.
// It would fail the validation if we didn't make sure the command argument is present,
// since it's required by the application.
if ($input->hasArgument('command') && null === $input->getArgument('command')) {
$input->setArgument('command', $this->getName());
}
$input->validate();
if ($this->code) {
$statusCode = ($this->code)($input, $output);
} else {
$statusCode = $this->execute($input, $output);
if (!\is_int($statusCode)) {
@trigger_error(sprintf('Return value of "%s::execute()" should always be of the type int since Symfony 4.4, %s returned.', \get_class($this), \gettype($statusCode)), E_USER_DEPRECATED);
}
}
return is_numeric($statusCode) ? (int) $statusCode : 0;
}
/**
* Sets the code to execute when running this command.
*
* If this method is used, it overrides the code defined
* in the execute() method.
*
* @param callable $code A callable(InputInterface $input, OutputInterface $output)
*
* @return $this
*
* @throws InvalidArgumentException
*
* @see execute()
*/
public function setCode(callable $code)
{
if ($code instanceof \Closure) {
$r = new \ReflectionFunction($code);
if (null === $r->getClosureThis()) {
$code = \Closure::bind($code, $this);
}
}
$this->code = $code;
return $this;
}
/**
* Merges the application definition with the command definition.
*
* This method is not part of public API and should not be used directly.
*
* @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
*/
public function mergeApplicationDefinition($mergeArgs = true)
{
if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) {
return;
}
$this->definition->addOptions($this->application->getDefinition()->getOptions());
$this->applicationDefinitionMerged = true;
if ($mergeArgs) {
$currentArguments = $this->definition->getArguments();
$this->definition->setArguments($this->application->getDefinition()->getArguments());
$this->definition->addArguments($currentArguments);
$this->applicationDefinitionMergedWithArgs = true;
}
}
/**
* Sets an array of argument and option instances.
*
* @param array|InputDefinition $definition An array of argument and option instances or a definition instance
*
* @return $this
*/
public function setDefinition($definition)
{
if ($definition instanceof InputDefinition) {
$this->definition = $definition;
} else {
$this->definition->setDefinition($definition);
}
$this->applicationDefinitionMerged = false;
return $this;
}
/**
* Gets the InputDefinition attached to this Command.
*
* @return InputDefinition An InputDefinition instance
*/
public function getDefinition()
{
if (null === $this->definition) {
throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', \get_class($this)));
}
return $this->definition;
}
/**
* Gets the InputDefinition to be used to create representations of this Command.
*
* Can be overridden to provide the original command representation when it would otherwise
* be changed by merging with the application InputDefinition.
*
* This method is not part of public API and should not be used directly.
*
* @return InputDefinition An InputDefinition instance
*/
public function getNativeDefinition()
{
return $this->getDefinition();
}
/**
* Adds an argument.
*
* @param string $name The argument name
* @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param string $description A description text
* @param string|string[]|null $default The default value (for InputArgument::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
*
* @return $this
*/
public function addArgument($name, $mode = null, $description = '', $default = null)
{
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
return $this;
}
/**
* Adds an option.
*
* @param string $name The option name
* @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int|null $mode The option mode: One of the InputOption::VALUE_* constants
* @param string $description A description text
* @param string|string[]|int|bool|null $default The default value (must be null for InputOption::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*
* @return $this
*/
public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null)
{
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
return $this;
}
/**
* Sets the name of the command.
*
* This method can set both the namespace and the name if
* you separate them by a colon (:)
*
* $command->setName('foo:bar');
*
* @param string $name The command name
*
* @return $this
*
* @throws InvalidArgumentException When the name is invalid
*/
public function setName($name)
{
$this->validateName($name);
$this->name = $name;
return $this;
}
/**
* Sets the process title of the command.
*
* This feature should be used only when creating a long process command,
* like a daemon.
*
* PHP 5.5+ or the proctitle PECL library is required
*
* @param string $title The process title
*
* @return $this
*/
public function setProcessTitle($title)
{
$this->processTitle = $title;
return $this;
}
/**
* Returns the command name.
*
* @return string|null
*/
public function getName()
{
return $this->name;
}
/**
* @param bool $hidden Whether or not the command should be hidden from the list of commands
*
* @return Command The current instance
*/
public function setHidden($hidden)
{
$this->hidden = (bool) $hidden;
return $this;
}
/**
* @return bool whether the command should be publicly shown or not
*/
public function isHidden()
{
return $this->hidden;
}
/**
* Sets the description for the command.
*
* @param string $description The description for the command
*
* @return $this
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Returns the description for the command.
*
* @return string The description for the command
*/
public function getDescription()
{
return $this->description;
}
/**
* Sets the help for the command.
*
* @param string $help The help for the command
*
* @return $this
*/
public function setHelp($help)
{
$this->help = $help;
return $this;
}
/**
* Returns the help for the command.
*
* @return string The help for the command
*/
public function getHelp()
{
return $this->help;
}
/**
* Returns the processed help for the command replacing the %command.name% and
* %command.full_name% patterns with the real values dynamically.
*
* @return string The processed help for the command
*/
public function getProcessedHelp()
{
$name = $this->name;
$isSingleCommand = $this->application && $this->application->isSingleCommand();
$placeholders = [
'%command.name%',
'%command.full_name%',
];
$replacements = [
$name,
$isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name,
];
return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
}
/**
* Sets the aliases for the command.
*
* @param string[] $aliases An array of aliases for the command
*
* @return $this
*
* @throws InvalidArgumentException When an alias is invalid
*/
public function setAliases($aliases)
{
if (!\is_array($aliases) && !$aliases instanceof \Traversable) {
throw new InvalidArgumentException('$aliases must be an array or an instance of \Traversable');
}
foreach ($aliases as $alias) {
$this->validateName($alias);
}
$this->aliases = $aliases;
return $this;
}
/**
* Returns the aliases for the command.
*
* @return array An array of aliases for the command
*/
public function getAliases()
{
return $this->aliases;
}
/**
* Returns the synopsis for the command.
*
* @param bool $short Whether to show the short version of the synopsis (with options folded) or not
*
* @return string The synopsis
*/
public function getSynopsis($short = false)
{
$key = $short ? 'short' : 'long';
if (!isset($this->synopsis[$key])) {
$this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
}
return $this->synopsis[$key];
}
/**
* Add a command usage example.
*
* @param string $usage The usage, it'll be prefixed with the command name
*
* @return $this
*/
public function addUsage($usage)
{
if (0 !== strpos($usage, $this->name)) {
$usage = sprintf('%s %s', $this->name, $usage);
}
$this->usages[] = $usage;
return $this;
}
/**
* Returns alternative usages of the command.
*
* @return array
*/
public function getUsages()
{
return $this->usages;
}
/**
* Gets a helper instance by name.
*
* @param string $name The helper name
*
* @return mixed The helper value
*
* @throws LogicException if no HelperSet is defined
* @throws InvalidArgumentException if the helper is not defined
*/
public function getHelper($name)
{
if (null === $this->helperSet) {
throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));
}
return $this->helperSet->get($name);
}
/**
* Validates a command name.
*
* It must be non-empty and parts can optionally be separated by ":".
*
* @throws InvalidArgumentException When the name is invalid
*/
private function validateName(string $name)
{
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Defines the styles for a Table.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Саша Стаменковић <umpirsky@gmail.com>
* @author Dany Maillard <danymaillard93b@gmail.com>
*/
class TableStyle
{
private $paddingChar = ' ';
private $horizontalOutsideBorderChar = '-';
private $horizontalInsideBorderChar = '-';
private $verticalOutsideBorderChar = '|';
private $verticalInsideBorderChar = '|';
private $crossingChar = '+';
private $crossingTopRightChar = '+';
private $crossingTopMidChar = '+';
private $crossingTopLeftChar = '+';
private $crossingMidRightChar = '+';
private $crossingBottomRightChar = '+';
private $crossingBottomMidChar = '+';
private $crossingBottomLeftChar = '+';
private $crossingMidLeftChar = '+';
private $crossingTopLeftBottomChar = '+';
private $crossingTopMidBottomChar = '+';
private $crossingTopRightBottomChar = '+';
private $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
private $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
private $cellHeaderFormat = '<info>%s</info>';
private $cellRowFormat = '%s';
private $cellRowContentFormat = ' %s ';
private $borderFormat = '%s';
private $padType = STR_PAD_RIGHT;
/**
* Sets padding character, used for cell padding.
*
* @param string $paddingChar
*
* @return $this
*/
public function setPaddingChar($paddingChar)
{
if (!$paddingChar) {
throw new LogicException('The padding char must not be empty');
}
$this->paddingChar = $paddingChar;
return $this;
}
/**
* Gets padding character, used for cell padding.
*
* @return string
*/
public function getPaddingChar()
{
return $this->paddingChar;
}
/**
* Sets horizontal border characters.
*
* <code>
* â•”â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╤â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╤â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•—
* 1 ISBN 2 Title │ Author ║
* â• â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╪â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╪â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•£
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
* ╚â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•§â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•§â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>
* </code>
*
* @param string $outside Outside border char (see #1 of example)
* @param string|null $inside Inside border char (see #2 of example), equals $outside if null
*/
public function setHorizontalBorderChars(string $outside, string $inside = null): self
{
$this->horizontalOutsideBorderChar = $outside;
$this->horizontalInsideBorderChar = $inside ?? $outside;
return $this;
}
/**
* Sets horizontal border character.
*
* @param string $horizontalBorderChar
*
* @return $this
*
* @deprecated since Symfony 4.1, use {@link setHorizontalBorderChars()} instead.
*/
public function setHorizontalBorderChar($horizontalBorderChar)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use setHorizontalBorderChars() instead.', __METHOD__), E_USER_DEPRECATED);
return $this->setHorizontalBorderChars($horizontalBorderChar, $horizontalBorderChar);
}
/**
* Gets horizontal border character.
*
* @return string
*
* @deprecated since Symfony 4.1, use {@link getBorderChars()} instead.
*/
public function getHorizontalBorderChar()
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use getBorderChars() instead.', __METHOD__), E_USER_DEPRECATED);
return $this->horizontalOutsideBorderChar;
}
/**
* Sets vertical border characters.
*
* <code>
* â•”â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╤â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╤â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•—
* ║ ISBN │ Title │ Author ║
* â• â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>1â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╪â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╪â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•£
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
* ╟───────2───────┼──────────────────────────┼──────────────────╢
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
* ╚â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•§â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•§â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>
* </code>
*
* @param string $outside Outside border char (see #1 of example)
* @param string|null $inside Inside border char (see #2 of example), equals $outside if null
*/
public function setVerticalBorderChars(string $outside, string $inside = null): self
{
$this->verticalOutsideBorderChar = $outside;
$this->verticalInsideBorderChar = $inside ?? $outside;
return $this;
}
/**
* Sets vertical border character.
*
* @param string $verticalBorderChar
*
* @return $this
*
* @deprecated since Symfony 4.1, use {@link setVerticalBorderChars()} instead.
*/
public function setVerticalBorderChar($verticalBorderChar)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use setVerticalBorderChars() instead.', __METHOD__), E_USER_DEPRECATED);
return $this->setVerticalBorderChars($verticalBorderChar, $verticalBorderChar);
}
/**
* Gets vertical border character.
*
* @return string
*
* @deprecated since Symfony 4.1, use {@link getBorderChars()} instead.
*/
public function getVerticalBorderChar()
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1, use getBorderChars() instead.', __METHOD__), E_USER_DEPRECATED);
return $this->verticalOutsideBorderChar;
}
/**
* Gets border characters.
*
* @internal
*/
public function getBorderChars(): array
{
return [
$this->horizontalOutsideBorderChar,
$this->verticalOutsideBorderChar,
$this->horizontalInsideBorderChar,
$this->verticalInsideBorderChar,
];
}
/**
* Sets crossing characters.
*
* Example:
* <code>
* 1â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>2â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>2â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>3
* ║ ISBN │ Title │ Author ║
* 8'â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>0'â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>0'â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>4'
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
* 8───────────────0──────────────────────────0──────────────────4
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
* 7â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>6â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>6â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>5
* </code>
*
* @param string $cross Crossing char (see #0 of example)
* @param string $topLeft Top left char (see #1 of example)
* @param string $topMid Top mid char (see #2 of example)
* @param string $topRight Top right char (see #3 of example)
* @param string $midRight Mid right char (see #4 of example)
* @param string $bottomRight Bottom right char (see #5 of example)
* @param string $bottomMid Bottom mid char (see #6 of example)
* @param string $bottomLeft Bottom left char (see #7 of example)
* @param string $midLeft Mid left char (see #8 of example)
* @param string|null $topLeftBottom Top left bottom char (see #8' of example), equals to $midLeft if null
* @param string|null $topMidBottom Top mid bottom char (see #0' of example), equals to $cross if null
* @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null
*/
public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): self
{
$this->crossingChar = $cross;
$this->crossingTopLeftChar = $topLeft;
$this->crossingTopMidChar = $topMid;
$this->crossingTopRightChar = $topRight;
$this->crossingMidRightChar = $midRight;
$this->crossingBottomRightChar = $bottomRight;
$this->crossingBottomMidChar = $bottomMid;
$this->crossingBottomLeftChar = $bottomLeft;
$this->crossingMidLeftChar = $midLeft;
$this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft;
$this->crossingTopMidBottomChar = $topMidBottom ?? $cross;
$this->crossingTopRightBottomChar = $topRightBottom ?? $midRight;
return $this;
}
/**
* Sets default crossing character used for each cross.
*
* @see {@link setCrossingChars()} for setting each crossing individually.
*/
public function setDefaultCrossingChar(string $char): self
{
return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char);
}
/**
* Sets crossing character.
*
* @param string $crossingChar
*
* @return $this
*
* @deprecated since Symfony 4.1. Use {@link setDefaultCrossingChar()} instead.
*/
public function setCrossingChar($crossingChar)
{
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1. Use setDefaultCrossingChar() instead.', __METHOD__), E_USER_DEPRECATED);
return $this->setDefaultCrossingChar($crossingChar);
}
/**
* Gets crossing character.
*
* @return string
*/
public function getCrossingChar()
{
return $this->crossingChar;
}
/**
* Gets crossing characters.
*
* @internal
*/
public function getCrossingChars(): array
{
return [
$this->crossingChar,
$this->crossingTopLeftChar,
$this->crossingTopMidChar,
$this->crossingTopRightChar,
$this->crossingMidRightChar,
$this->crossingBottomRightChar,
$this->crossingBottomMidChar,
$this->crossingBottomLeftChar,
$this->crossingMidLeftChar,
$this->crossingTopLeftBottomChar,
$this->crossingTopMidBottomChar,
$this->crossingTopRightBottomChar,
];
}
/**
* Sets header cell format.
*
* @param string $cellHeaderFormat
*
* @return $this
*/
public function setCellHeaderFormat($cellHeaderFormat)
{
$this->cellHeaderFormat = $cellHeaderFormat;
return $this;
}
/**
* Gets header cell format.
*
* @return string
*/
public function getCellHeaderFormat()
{
return $this->cellHeaderFormat;
}
/**
* Sets row cell format.
*
* @param string $cellRowFormat
*
* @return $this
*/
public function setCellRowFormat($cellRowFormat)
{
$this->cellRowFormat = $cellRowFormat;
return $this;
}
/**
* Gets row cell format.
*
* @return string
*/
public function getCellRowFormat()
{
return $this->cellRowFormat;
}
/**
* Sets row cell content format.
*
* @param string $cellRowContentFormat
*
* @return $this
*/
public function setCellRowContentFormat($cellRowContentFormat)
{
$this->cellRowContentFormat = $cellRowContentFormat;
return $this;
}
/**
* Gets row cell content format.
*
* @return string
*/
public function getCellRowContentFormat()
{
return $this->cellRowContentFormat;
}
/**
* Sets table border format.
*
* @param string $borderFormat
*
* @return $this
*/
public function setBorderFormat($borderFormat)
{
$this->borderFormat = $borderFormat;
return $this;
}
/**
* Gets table border format.
*
* @return string
*/
public function getBorderFormat()
{
return $this->borderFormat;
}
/**
* Sets cell padding type.
*
* @param int $padType STR_PAD_*
*
* @return $this
*/
public function setPadType($padType)
{
if (!\in_array($padType, [STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH], true)) {
throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
}
$this->padType = $padType;
return $this;
}
/**
* Gets cell padding type.
*
* @return int
*/
public function getPadType()
{
return $this->padType;
}
public function getHeaderTitleFormat(): string
{
return $this->headerTitleFormat;
}
public function setHeaderTitleFormat(string $format): self
{
$this->headerTitleFormat = $format;
return $this;
}
public function getFooterTitleFormat(): string
{
return $this->footerTitleFormat;
}
public function setFooterTitleFormat(string $format): self
{
$this->footerTitleFormat = $format;
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* @internal
*/
class TableRows implements \IteratorAggregate
{
private $generator;
public function __construct(callable $generator)
{
$this->generator = $generator;
}
public function getIterator(): \Traversable
{
$g = $this->generator;
return $g();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Symfony Style Guide compliant question helper.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class SymfonyQuestionHelper extends QuestionHelper
{
/**
* {@inheritdoc}
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
$default = $question->getDefault();
switch (true) {
case null === $default:
$text = sprintf(' <info>%s</info>:', $text);
break;
case $question instanceof ConfirmationQuestion:
$text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');
break;
case $question instanceof ChoiceQuestion && $question->isMultiselect():
$choices = $question->getChoices();
$default = explode(',', $default);
foreach ($default as $key => $value) {
$default[$key] = $choices[trim($value)];
}
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));
break;
case $question instanceof ChoiceQuestion:
$choices = $question->getChoices();
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(isset($choices[$default]) ? $choices[$default] : $default));
break;
default:
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
}
$output->writeln($text);
$prompt = ' > ';
if ($question instanceof ChoiceQuestion) {
$output->writeln($this->formatChoiceQuestionChoices($question, 'comment'));
$prompt = $question->getPrompt();
}
$output->write($prompt);
}
/**
* {@inheritdoc}
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if ($output instanceof SymfonyStyle) {
$output->newLine();
$output->error($error->getMessage());
return;
}
parent::writeError($output, $error);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* HelperInterface is the interface all helpers must implement.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface HelperInterface
{
/**
* Sets the helper set associated with this helper.
*/
public function setHelperSet(HelperSet $helperSet = null);
/**
* Gets the helper set associated with this helper.
*
* @return HelperSet A HelperSet instance
*/
public function getHelperSet();
/**
* Returns the canonical name of this helper.
*
* @return string The canonical name
*/
public function getName();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class TableCell
{
private $value;
private $options = [
'rowspan' => 1,
'colspan' => 1,
];
public function __construct(string $value = '', array $options = [])
{
$this->value = $value;
// check option names
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
}
$this->options = array_merge($this->options, $options);
}
/**
* Returns the cell value.
*
* @return string
*/
public function __toString()
{
return $this->value;
}
/**
* Gets number of colspan.
*
* @return int
*/
public function getColspan()
{
return (int) $this->options['colspan'];
}
/**
* Gets number of rowspan.
*
* @return int
*/
public function getRowspan()
{
return (int) $this->options['rowspan'];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
/**
* The ProcessHelper class provides helpers to run external processes.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since Symfony 4.2
*/
class ProcessHelper extends Helper
{
/**
* Runs an external process.
*
* @param array|Process $cmd An instance of Process or an array of the command and arguments
* @param string|null $error An error message that must be displayed if something went wrong
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
* @param int $verbosity The threshold for verbosity
*
* @return Process The process that ran
*/
public function run(OutputInterface $output, $cmd, $error = null, callable $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$formatter = $this->getHelperSet()->get('debug_formatter');
if ($cmd instanceof Process) {
$cmd = [$cmd];
}
if (!\is_array($cmd)) {
@trigger_error(sprintf('Passing a command as a string to "%s()" is deprecated since Symfony 4.2, pass it the command as an array of arguments instead.', __METHOD__), E_USER_DEPRECATED);
$cmd = [method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline($cmd) : new Process($cmd)];
}
if (\is_string($cmd[0] ?? null)) {
$process = new Process($cmd);
$cmd = [];
} elseif (($cmd[0] ?? null) instanceof Process) {
$process = $cmd[0];
unset($cmd[0]);
} else {
throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__));
}
if ($verbosity <= $output->getVerbosity()) {
$output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
}
if ($output->isDebug()) {
$callback = $this->wrapCallback($output, $process, $callback);
}
$process->run($callback, $cmd);
if ($verbosity <= $output->getVerbosity()) {
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
}
if (!$process->isSuccessful() && null !== $error) {
$output->writeln(sprintf('<error>%s</error>', $this->escapeString($error)));
}
return $process;
}
/**
* Runs the process.
*
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
*
* @param string|Process $cmd An instance of Process or a command to run
* @param string|null $error An error message that must be displayed if something went wrong
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return Process The process that ran
*
* @throws ProcessFailedException
*
* @see run()
*/
public function mustRun(OutputInterface $output, $cmd, $error = null, callable $callback = null)
{
$process = $this->run($output, $cmd, $error, $callback);
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
return $process;
}
/**
* Wraps a Process callback to add debugging output.
*
* @return callable
*/
public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$formatter = $this->getHelperSet()->get('debug_formatter');
return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
$output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));
if (null !== $callback) {
$callback($type, $buffer);
}
};
}
private function escapeString(string $str): string
{
return str_replace('<', '\\<', $str);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'process';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* Marks a row as being a separator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TableSeparator extends TableCell
{
public function __construct(array $options = [])
{
parent::__construct('', $options);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Formatter\OutputFormatter;
/**
* The Formatter class provides helpers to format messages.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FormatterHelper extends Helper
{
/**
* Formats a message within a section.
*
* @param string $section The section name
* @param string $message The message
* @param string $style The style to apply to the section
*
* @return string The format section
*/
public function formatSection($section, $message, $style = 'info')
{
return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
}
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
* @param string $style The style to apply to the whole block
* @param bool $large Whether to return a large block
*
* @return string The formatter message
*/
public function formatBlock($messages, $style, $large = false)
{
if (!\is_array($messages)) {
$messages = [$messages];
}
$len = 0;
$lines = [];
foreach ($messages as $message) {
$message = OutputFormatter::escape($message);
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
$len = max(self::strlen($message) + ($large ? 4 : 2), $len);
}
$messages = $large ? [str_repeat(' ', $len)] : [];
for ($i = 0; isset($lines[$i]); ++$i) {
$messages[] = $lines[$i].str_repeat(' ', $len - self::strlen($lines[$i]));
}
if ($large) {
$messages[] = str_repeat(' ', $len);
}
for ($i = 0; isset($messages[$i]); ++$i) {
$messages[$i] = sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
}
return implode("\n", $messages);
}
/**
* Truncates a message to the given length.
*
* @param string $message
* @param int $length
* @param string $suffix
*
* @return string
*/
public function truncate($message, $length, $suffix = '...')
{
$computedLength = $length - self::strlen($suffix);
if ($computedLength > self::strlen($message)) {
return $message;
}
return self::substr($message, 0, $length).$suffix;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'formatter';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;
/**
* The ProgressBar provides helpers to display progress output.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Chris Jones <leeked@gmail.com>
*/
final class ProgressBar
{
private $barWidth = 28;
private $barChar;
private $emptyBarChar = '-';
private $progressChar = '>';
private $format;
private $internalFormat;
private $redrawFreq = 1;
private $writeCount;
private $lastWriteTime;
private $minSecondsBetweenRedraws = 0;
private $maxSecondsBetweenRedraws = 1;
private $output;
private $step = 0;
private $max;
private $startTime;
private $stepWidth;
private $percent = 0.0;
private $formatLineCount;
private $messages = [];
private $overwrite = true;
private $terminal;
private $previousMessage;
private static $formatters;
private static $formats;
/**
* @param int $max Maximum steps (0 if unknown)
*/
public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 0.1)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->output = $output;
$this->setMaxSteps($max);
$this->terminal = new Terminal();
if (0 < $minSecondsBetweenRedraws) {
$this->redrawFreq = null;
$this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
}
if (!$this->output->isDecorated()) {
// disable overwrite when output does not support ANSI codes.
$this->overwrite = false;
// set a reasonable redraw frequency so output isn't flooded
$this->redrawFreq = null;
}
$this->startTime = time();
}
/**
* Sets a placeholder formatter for a given name.
*
* This method also allow you to override an existing placeholder.
*
* @param string $name The placeholder name (including the delimiter char like %)
* @param callable $callable A PHP callable
*/
public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters[$name] = $callable;
}
/**
* Gets the placeholder formatter for a given name.
*
* @param string $name The placeholder name (including the delimiter char like %)
*
* @return callable|null A PHP callable
*/
public static function getPlaceholderFormatterDefinition(string $name): ?callable
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
}
/**
* Sets a format for a given name.
*
* This method also allow you to override an existing format.
*
* @param string $name The format name
* @param string $format A format string
*/
public static function setFormatDefinition(string $name, string $format): void
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
self::$formats[$name] = $format;
}
/**
* Gets the format for a given name.
*
* @param string $name The format name
*
* @return string|null A format string
*/
public static function getFormatDefinition(string $name): ?string
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
return isset(self::$formats[$name]) ? self::$formats[$name] : null;
}
/**
* Associates a text with a named placeholder.
*
* The text is displayed when the progress bar is rendered but only
* when the corresponding placeholder is part of the custom format line
* (by wrapping the name with %).
*
* @param string $message The text to associate with the placeholder
* @param string $name The name of the placeholder
*/
public function setMessage(string $message, string $name = 'message')
{
$this->messages[$name] = $message;
}
public function getMessage(string $name = 'message')
{
return $this->messages[$name];
}
public function getStartTime(): int
{
return $this->startTime;
}
public function getMaxSteps(): int
{
return $this->max;
}
public function getProgress(): int
{
return $this->step;
}
private function getStepWidth(): int
{
return $this->stepWidth;
}
public function getProgressPercent(): float
{
return $this->percent;
}
public function getBarOffset(): int
{
return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? min(5, $this->barWidth / 15) * $this->writeCount : $this->step) % $this->barWidth);
}
public function setBarWidth(int $size)
{
$this->barWidth = max(1, $size);
}
public function getBarWidth(): int
{
return $this->barWidth;
}
public function setBarCharacter(string $char)
{
$this->barChar = $char;
}
public function getBarCharacter(): string
{
if (null === $this->barChar) {
return $this->max ? '=' : $this->emptyBarChar;
}
return $this->barChar;
}
public function setEmptyBarCharacter(string $char)
{
$this->emptyBarChar = $char;
}
public function getEmptyBarCharacter(): string
{
return $this->emptyBarChar;
}
public function setProgressCharacter(string $char)
{
$this->progressChar = $char;
}
public function getProgressCharacter(): string
{
return $this->progressChar;
}
public function setFormat(string $format)
{
$this->format = null;
$this->internalFormat = $format;
}
/**
* Sets the redraw frequency.
*
* @param int|float $freq The frequency in steps
*/
public function setRedrawFrequency(?int $freq)
{
$this->redrawFreq = null !== $freq ? max(1, $freq) : null;
}
public function minSecondsBetweenRedraws(float $seconds): void
{
$this->minSecondsBetweenRedraws = $seconds;
}
public function maxSecondsBetweenRedraws(float $seconds): void
{
$this->maxSecondsBetweenRedraws = $seconds;
}
/**
* Returns an iterator that will automatically update the progress bar when iterated.
*
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable
*/
public function iterate(iterable $iterable, int $max = null): iterable
{
$this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0));
foreach ($iterable as $key => $value) {
yield $key => $value;
$this->advance();
}
$this->finish();
}
/**
* Starts the progress output.
*
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
*/
public function start(int $max = null)
{
$this->startTime = time();
$this->step = 0;
$this->percent = 0.0;
if (null !== $max) {
$this->setMaxSteps($max);
}
$this->display();
}
/**
* Advances the progress output X steps.
*
* @param int $step Number of steps to advance
*/
public function advance(int $step = 1)
{
$this->setProgress($this->step + $step);
}
/**
* Sets whether to overwrite the progressbar, false for new line.
*/
public function setOverwrite(bool $overwrite)
{
$this->overwrite = $overwrite;
}
public function setProgress(int $step)
{
if ($this->max && $step > $this->max) {
$this->max = $step;
} elseif ($step < 0) {
$step = 0;
}
$redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10);
$prevPeriod = (int) ($this->step / $redrawFreq);
$currPeriod = (int) ($step / $redrawFreq);
$this->step = $step;
$this->percent = $this->max ? (float) $this->step / $this->max : 0;
$timeInterval = microtime(true) - $this->lastWriteTime;
// Draw regardless of other limits
if ($this->max === $step) {
$this->display();
return;
}
// Throttling
if ($timeInterval < $this->minSecondsBetweenRedraws) {
return;
}
// Draw each step period, but not too late
if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
$this->display();
}
}
public function setMaxSteps(int $max)
{
$this->format = null;
$this->max = max(0, $max);
$this->stepWidth = $this->max ? Helper::strlen((string) $this->max) : 4;
}
/**
* Finishes the progress output.
*/
public function finish(): void
{
if (!$this->max) {
$this->max = $this->step;
}
if ($this->step === $this->max && !$this->overwrite) {
// prevent double 100% output
return;
}
$this->setProgress($this->max);
}
/**
* Outputs the current progress string.
*/
public function display(): void
{
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return;
}
if (null === $this->format) {
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
}
$this->overwrite($this->buildLine());
}
/**
* Removes the progress bar from the current line.
*
* This is useful if you wish to write some output
* while a progress bar is running.
* Call display() to show the progress bar again.
*/
public function clear(): void
{
if (!$this->overwrite) {
return;
}
if (null === $this->format) {
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
}
$this->overwrite('');
}
private function setRealFormat(string $format)
{
// try to use the _nomax variant if available
if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
$this->format = self::getFormatDefinition($format.'_nomax');
} elseif (null !== self::getFormatDefinition($format)) {
$this->format = self::getFormatDefinition($format);
} else {
$this->format = $format;
}
$this->formatLineCount = substr_count($this->format, "\n");
}
/**
* Overwrites a previous message to the output.
*/
private function overwrite(string $message): void
{
if ($this->previousMessage === $message) {
return;
}
$originalMessage = $message;
if ($this->overwrite) {
if (null !== $this->previousMessage) {
if ($this->output instanceof ConsoleSectionOutput) {
$lines = floor(Helper::strlen($message) / $this->terminal->getWidth()) + $this->formatLineCount + 1;
$this->output->clear($lines);
} else {
// Erase previous lines
if ($this->formatLineCount > 0) {
$message = str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount).$message;
}
// Move the cursor to the beginning of the line and erase the line
$message = "\x0D\x1B[2K$message";
}
}
} elseif ($this->step > 0) {
$message = PHP_EOL.$message;
}
$this->previousMessage = $originalMessage;
$this->lastWriteTime = microtime(true);
$this->output->write($message);
++$this->writeCount;
}
private function determineBestFormat(): string
{
switch ($this->output->getVerbosity()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
case OutputInterface::VERBOSITY_VERBOSE:
return $this->max ? 'verbose' : 'verbose_nomax';
case OutputInterface::VERBOSITY_VERY_VERBOSE:
return $this->max ? 'very_verbose' : 'very_verbose_nomax';
case OutputInterface::VERBOSITY_DEBUG:
return $this->max ? 'debug' : 'debug_nomax';
default:
return $this->max ? 'normal' : 'normal_nomax';
}
}
private static function initPlaceholderFormatters(): array
{
return [
'bar' => function (self $bar, OutputInterface $output) {
$completeBars = $bar->getBarOffset();
$display = str_repeat($bar->getBarCharacter(), $completeBars);
if ($completeBars < $bar->getBarWidth()) {
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
$display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
}
return $display;
},
'elapsed' => function (self $bar) {
return Helper::formatTime(time() - $bar->getStartTime());
},
'remaining' => function (self $bar) {
if (!$bar->getMaxSteps()) {
throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
}
if (!$bar->getProgress()) {
$remaining = 0;
} else {
$remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress()));
}
return Helper::formatTime($remaining);
},
'estimated' => function (self $bar) {
if (!$bar->getMaxSteps()) {
throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
}
if (!$bar->getProgress()) {
$estimated = 0;
} else {
$estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps());
}
return Helper::formatTime($estimated);
},
'memory' => function (self $bar) {
return Helper::formatMemory(memory_get_usage(true));
},
'current' => function (self $bar) {
return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
},
'max' => function (self $bar) {
return $bar->getMaxSteps();
},
'percent' => function (self $bar) {
return floor($bar->getProgressPercent() * 100);
},
];
}
private static function initFormats(): array
{
return [
'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
'normal_nomax' => ' %current% [%bar%]',
'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
];
}
private function buildLine(): string
{
$regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
$callback = function ($matches) {
if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {
$text = $formatter($this, $this->output);
} elseif (isset($this->messages[$matches[1]])) {
$text = $this->messages[$matches[1]];
} else {
return $matches[0];
}
if (isset($matches[2])) {
$text = sprintf('%'.$matches[2], $text);
}
return $text;
};
$line = preg_replace_callback($regex, $callback, $this->format);
// gets string length for each sub line with multiline format
$linesLength = array_map(function ($subLine) {
return Helper::strlenWithoutDecoration($this->output->getFormatter(), rtrim($subLine, "\r"));
}, explode("\n", $line));
$linesWidth = max($linesLength);
$terminalWidth = $this->terminal->getWidth();
if ($linesWidth <= $terminalWidth) {
return $line;
}
$this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth);
return preg_replace_callback($regex, $callback, $this->format);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* Helps outputting debug information when running an external program from a command.
*
* An external program can be a Process, an HTTP request, or anything else.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DebugFormatterHelper extends Helper
{
private $colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'];
private $started = [];
private $count = -1;
/**
* Starts a debug formatting session.
*
* @param string $id The id of the formatting session
* @param string $message The message to display
* @param string $prefix The prefix to use
*
* @return string
*/
public function start($id, $message, $prefix = 'RUN')
{
$this->started[$id] = ['border' => ++$this->count % \count($this->colors)];
return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
}
/**
* Adds progress to a formatting session.
*
* @param string $id The id of the formatting session
* @param string $buffer The message to display
* @param bool $error Whether to consider the buffer as error
* @param string $prefix The prefix for output
* @param string $errorPrefix The prefix for error output
*
* @return string
*/
public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR')
{
$message = '';
if ($error) {
if (isset($this->started[$id]['out'])) {
$message .= "\n";
unset($this->started[$id]['out']);
}
if (!isset($this->started[$id]['err'])) {
$message .= sprintf('%s<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix);
$this->started[$id]['err'] = true;
}
$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
} else {
if (isset($this->started[$id]['err'])) {
$message .= "\n";
unset($this->started[$id]['err']);
}
if (!isset($this->started[$id]['out'])) {
$message .= sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix);
$this->started[$id]['out'] = true;
}
$message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
}
return $message;
}
/**
* Stops a formatting session.
*
* @param string $id The id of the formatting session
* @param string $message The message to display
* @param bool $successful Whether to consider the result as success
* @param string $prefix The prefix for the end output
*
* @return string
*/
public function stop($id, $message, $successful, $prefix = 'RES')
{
$trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';
if ($successful) {
return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
}
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
unset($this->started[$id]['out'], $this->started[$id]['err']);
return $message;
}
private function getBorder(string $id): string
{
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'debug_formatter';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* HelperSet represents a set of helpers to be used with a command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HelperSet implements \IteratorAggregate
{
/**
* @var Helper[]
*/
private $helpers = [];
private $command;
/**
* @param Helper[] $helpers An array of helper
*/
public function __construct(array $helpers = [])
{
foreach ($helpers as $alias => $helper) {
$this->set($helper, \is_int($alias) ? null : $alias);
}
}
/**
* Sets a helper.
*
* @param string $alias An alias
*/
public function set(HelperInterface $helper, $alias = null)
{
$this->helpers[$helper->getName()] = $helper;
if (null !== $alias) {
$this->helpers[$alias] = $helper;
}
$helper->setHelperSet($this);
}
/**
* Returns true if the helper if defined.
*
* @param string $name The helper name
*
* @return bool true if the helper is defined, false otherwise
*/
public function has($name)
{
return isset($this->helpers[$name]);
}
/**
* Gets a helper value.
*
* @param string $name The helper name
*
* @return HelperInterface The helper instance
*
* @throws InvalidArgumentException if the helper is not defined
*/
public function get($name)
{
if (!$this->has($name)) {
throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
}
return $this->helpers[$name];
}
public function setCommand(Command $command = null)
{
$this->command = $command;
}
/**
* Gets the command associated with this helper set.
*
* @return Command A Command instance
*/
public function getCommand()
{
return $this->command;
}
/**
* @return Helper[]
*/
public function getIterator()
{
return new \ArrayIterator($this->helpers);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
/**
* @author Roland Franssen <franssen.roland@gmail.com>
*/
final class Dumper
{
private $output;
private $dumper;
private $cloner;
private $handler;
public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null)
{
$this->output = $output;
$this->dumper = $dumper;
$this->cloner = $cloner;
if (class_exists(CliDumper::class)) {
$this->handler = function ($var): string {
$dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
$dumper->setColors($this->output->isDecorated());
return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true));
};
} else {
$this->handler = function ($var): string {
switch (true) {
case null === $var:
return 'null';
case true === $var:
return 'true';
case false === $var:
return 'false';
case \is_string($var):
return '"'.$var.'"';
default:
return rtrim(print_r($var, true));
}
};
}
}
public function __invoke($var): string
{
return ($this->handler)($var);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputInterface;
/**
* An implementation of InputAwareInterface for Helpers.
*
* @author Wouter J <waldio.webdesign@gmail.com>
*/
abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
protected $input;
/**
* {@inheritdoc}
*/
public function setInput(InputInterface $input)
{
$this->input = $input;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;
/**
* The QuestionHelper class provides helpers to interact with the user.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class QuestionHelper extends Helper
{
private $inputStream;
private static $shell;
private static $stty;
/**
* Asks a question to the user.
*
* @return mixed The user answer
*
* @throws RuntimeException If there is no data to read in the input stream
*/
public function ask(InputInterface $input, OutputInterface $output, Question $question)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
if (!$input->isInteractive()) {
$default = $question->getDefault();
if (null === $default) {
return $default;
}
if ($validator = $question->getValidator()) {
return \call_user_func($question->getValidator(), $default);
} elseif ($question instanceof ChoiceQuestion) {
$choices = $question->getChoices();
if (!$question->isMultiselect()) {
return isset($choices[$default]) ? $choices[$default] : $default;
}
$default = explode(',', $default);
foreach ($default as $k => $v) {
$v = $question->isTrimmable() ? trim($v) : $v;
$default[$k] = isset($choices[$v]) ? $choices[$v] : $v;
}
}
return $default;
}
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
$this->inputStream = $stream;
}
if (!$question->getValidator()) {
return $this->doAsk($output, $question);
}
$interviewer = function () use ($output, $question) {
return $this->doAsk($output, $question);
};
return $this->validateAttempts($interviewer, $output, $question);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'question';
}
/**
* Prevents usage of stty.
*/
public static function disableStty()
{
self::$stty = false;
}
/**
* Asks the question to the user.
*
* @return bool|mixed|string|null
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function doAsk(OutputInterface $output, Question $question)
{
$this->writePrompt($output, $question);
$inputStream = $this->inputStream ?: STDIN;
$autocomplete = $question->getAutocompleterCallback();
if (null === $autocomplete || !Terminal::hasSttyAvailable()) {
$ret = false;
if ($question->isHidden()) {
try {
$hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
$ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse;
} catch (RuntimeException $e) {
if (!$question->isHiddenFallback()) {
throw $e;
}
}
}
if (false === $ret) {
$ret = fgets($inputStream, 4096);
if (false === $ret) {
throw new RuntimeException('Aborted.');
}
if ($question->isTrimmable()) {
$ret = trim($ret);
}
}
} else {
$autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
$ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
}
if ($output instanceof ConsoleSectionOutput) {
$output->addContent($ret);
}
$ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
if ($normalizer = $question->getNormalizer()) {
return $normalizer($ret);
}
return $ret;
}
/**
* Outputs the question prompt.
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$message = $question->getQuestion();
if ($question instanceof ChoiceQuestion) {
$output->writeln(array_merge([
$question->getQuestion(),
], $this->formatChoiceQuestionChoices($question, 'info')));
$message = $question->getPrompt();
}
$output->write($message);
}
/**
* @param string $tag
*
* @return string[]
*/
protected function formatChoiceQuestionChoices(ChoiceQuestion $question, $tag)
{
$messages = [];
$maxWidth = max(array_map('self::strlen', array_keys($choices = $question->getChoices())));
foreach ($choices as $key => $value) {
$padding = str_repeat(' ', $maxWidth - self::strlen($key));
$messages[] = sprintf(" [<$tag>%s$padding</$tag>] %s", $key, $value);
}
return $messages;
}
/**
* Outputs an error message.
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
$message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
} else {
$message = '<error>'.$error->getMessage().'</error>';
}
$output->writeln($message);
}
/**
* Autocompletes a question.
*
* @param resource $inputStream
*/
private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
{
$fullChoice = '';
$ret = '';
$i = 0;
$ofs = -1;
$matches = $autocomplete($ret);
$numMatches = \count($matches);
$sttyMode = shell_exec('stty -g');
// Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
shell_exec('stty -icanon -echo');
// Add highlighted text style
$output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
// Read a keypress
while (!feof($inputStream)) {
$c = fread($inputStream, 1);
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
shell_exec(sprintf('stty %s', $sttyMode));
throw new RuntimeException('Aborted.');
} elseif ("\177" === $c) { // Backspace Character
if (0 === $numMatches && 0 !== $i) {
--$i;
$fullChoice = self::substr($fullChoice, 0, $i);
// Move cursor backwards
$output->write("\033[1D");
}
if (0 === $i) {
$ofs = -1;
$matches = $autocomplete($ret);
$numMatches = \count($matches);
} else {
$numMatches = 0;
}
// Pop the last character off the end of our string
$ret = self::substr($ret, 0, $i);
} elseif ("\033" === $c) {
// Did we read an escape sequence?
$c .= fread($inputStream, 2);
// A = Up Arrow. B = Down Arrow
if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
if ('A' === $c[2] && -1 === $ofs) {
$ofs = 0;
}
if (0 === $numMatches) {
continue;
}
$ofs += ('A' === $c[2]) ? -1 : 1;
$ofs = ($numMatches + $ofs) % $numMatches;
}
} elseif (\ord($c) < 32) {
if ("\t" === $c || "\n" === $c) {
if ($numMatches > 0 && -1 !== $ofs) {
$ret = (string) $matches[$ofs];
// Echo out remaining chars for current match
$remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
$output->write($remainingCharacters);
$fullChoice .= $remainingCharacters;
$i = self::strlen($fullChoice);
$matches = array_filter(
$autocomplete($ret),
function ($match) use ($ret) {
return '' === $ret || 0 === strpos($match, $ret);
}
);
$numMatches = \count($matches);
$ofs = -1;
}
if ("\n" === $c) {
$output->write($c);
break;
}
$numMatches = 0;
}
continue;
} else {
if ("\x80" <= $c) {
$c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
}
$output->write($c);
$ret .= $c;
$fullChoice .= $c;
++$i;
$tempRet = $ret;
if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
$tempRet = $this->mostRecentlyEnteredValue($fullChoice);
}
$numMatches = 0;
$ofs = 0;
foreach ($autocomplete($ret) as $value) {
// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
if (0 === strpos($value, $tempRet)) {
$matches[$numMatches++] = $value;
}
}
}
// Erase characters from cursor to end of line
$output->write("\033[K");
if ($numMatches > 0 && -1 !== $ofs) {
// Save cursor position
$output->write("\0337");
// Write highlighted text, complete the partially entered response
$charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
// Restore cursor position
$output->write("\0338");
}
}
// Reset stty so it behaves normally again
shell_exec(sprintf('stty %s', $sttyMode));
return $fullChoice;
}
private function mostRecentlyEnteredValue(string $entered): string
{
// Determine the most recent value that the user entered
if (false === strpos($entered, ',')) {
return $entered;
}
$choices = explode(',', $entered);
if (\strlen($lastChoice = trim($choices[\count($choices) - 1])) > 0) {
return $lastChoice;
}
return $entered;
}
/**
* Gets a hidden response from user.
*
* @param resource $inputStream The handler resource
* @param bool $trimmable Is the answer trimmable
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
// handle code running from a phar
if ('phar:' === substr(__FILE__, 0, 5)) {
$tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
copy($exe, $tmpExe);
$exe = $tmpExe;
}
$sExec = shell_exec($exe);
$value = $trimmable ? rtrim($sExec) : $sExec;
$output->writeln('');
if (isset($tmpExe)) {
unlink($tmpExe);
}
return $value;
}
if (Terminal::hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
shell_exec('stty -echo');
$value = fgets($inputStream, 4096);
shell_exec(sprintf('stty %s', $sttyMode));
if (false === $value) {
throw new RuntimeException('Aborted.');
}
if ($trimmable) {
$value = trim($value);
}
$output->writeln('');
return $value;
}
if (false !== $shell = $this->getShell()) {
$readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword';
$command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
$sCommand = shell_exec($command);
$value = $trimmable ? rtrim($sCommand) : $sCommand;
$output->writeln('');
return $value;
}
throw new RuntimeException('Unable to hide the response.');
}
/**
* Validates an attempt.
*
* @param callable $interviewer A callable that will ask for a question and return the result
*
* @return mixed The validated response
*
* @throws \Exception In case the max number of attempts has been reached and no valid response has been given
*/
private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
{
$error = null;
$attempts = $question->getMaxAttempts();
while (null === $attempts || $attempts--) {
if (null !== $error) {
$this->writeError($output, $error);
}
try {
return $question->getValidator()($interviewer());
} catch (RuntimeException $e) {
throw $e;
} catch (\Exception $error) {
}
}
throw $error;
}
/**
* Returns a valid unix shell.
*
* @return string|bool The valid shell name, false in case no valid shell is found
*/
private function getShell()
{
if (null !== self::$shell) {
return self::$shell;
}
self::$shell = false;
if (file_exists('/usr/bin/env')) {
// handle other OSs with bash/zsh/ksh/csh if available to hide the answer
$test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) {
if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
self::$shell = $sh;
break;
}
}
}
return self::$shell;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* Helper is the base class for all helper classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Helper implements HelperInterface
{
protected $helperSet = null;
/**
* {@inheritdoc}
*/
public function setHelperSet(HelperSet $helperSet = null)
{
$this->helperSet = $helperSet;
}
/**
* {@inheritdoc}
*/
public function getHelperSet()
{
return $this->helperSet;
}
/**
* Returns the length of a string, using mb_strwidth if it is available.
*
* @param string $string The string to check its length
*
* @return int The length of the string
*/
public static function strlen($string)
{
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return \strlen($string);
}
return mb_strwidth($string, $encoding);
}
/**
* Returns the subset of a string, using mb_substr if it is available.
*
* @param string $string String to subset
* @param int $from Start offset
* @param int|null $length Length to read
*
* @return string The string subset
*/
public static function substr($string, $from, $length = null)
{
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return substr($string, $from, $length);
}
return mb_substr($string, $from, $length, $encoding);
}
public static function formatTime($secs)
{
static $timeFormats = [
[0, '< 1 sec'],
[1, '1 sec'],
[2, 'secs', 1],
[60, '1 min'],
[120, 'mins', 60],
[3600, '1 hr'],
[7200, 'hrs', 3600],
[86400, '1 day'],
[172800, 'days', 86400],
];
foreach ($timeFormats as $index => $format) {
if ($secs >= $format[0]) {
if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0])
|| $index == \count($timeFormats) - 1
) {
if (2 == \count($format)) {
return $format[1];
}
return floor($secs / $format[2]).' '.$format[1];
}
}
}
}
public static function formatMemory($memory)
{
if ($memory >= 1024 * 1024 * 1024) {
return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
}
if ($memory >= 1024 * 1024) {
return sprintf('%.1f MiB', $memory / 1024 / 1024);
}
if ($memory >= 1024) {
return sprintf('%d KiB', $memory / 1024);
}
return sprintf('%d B', $memory);
}
public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string)
{
return self::strlen(self::removeDecoration($formatter, $string));
}
public static function removeDecoration(OutputFormatterInterface $formatter, $string)
{
$isDecorated = $formatter->isDecorated();
$formatter->setDecorated(false);
// remove <...> formatting
$string = $formatter->format($string);
// remove already formatted characters
$string = preg_replace("/\033\[[^m]*m/", '', $string);
$formatter->setDecorated($isDecorated);
return $string;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Provides helpers to display a table.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Саша Стаменковић <umpirsky@gmail.com>
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
* @author Max Grigorian <maxakawizard@gmail.com>
* @author Dany Maillard <danymaillard93b@gmail.com>
*/
class Table
{
private const SEPARATOR_TOP = 0;
private const SEPARATOR_TOP_BOTTOM = 1;
private const SEPARATOR_MID = 2;
private const SEPARATOR_BOTTOM = 3;
private const BORDER_OUTSIDE = 0;
private const BORDER_INSIDE = 1;
private $headerTitle;
private $footerTitle;
/**
* Table headers.
*/
private $headers = [];
/**
* Table rows.
*/
private $rows = [];
private $horizontal = false;
/**
* Column widths cache.
*/
private $effectiveColumnWidths = [];
/**
* Number of columns cache.
*
* @var int
*/
private $numberOfColumns;
/**
* @var OutputInterface
*/
private $output;
/**
* @var TableStyle
*/
private $style;
/**
* @var array
*/
private $columnStyles = [];
/**
* User set column widths.
*
* @var array
*/
private $columnWidths = [];
private $columnMaxWidths = [];
private static $styles;
private $rendered = false;
public function __construct(OutputInterface $output)
{
$this->output = $output;
if (!self::$styles) {
self::$styles = self::initStyles();
}
$this->setStyle('default');
}
/**
* Sets a style definition.
*
* @param string $name The style name
*/
public static function setStyleDefinition($name, TableStyle $style)
{
if (!self::$styles) {
self::$styles = self::initStyles();
}
self::$styles[$name] = $style;
}
/**
* Gets a style definition by name.
*
* @param string $name The style name
*
* @return TableStyle
*/
public static function getStyleDefinition($name)
{
if (!self::$styles) {
self::$styles = self::initStyles();
}
if (isset(self::$styles[$name])) {
return self::$styles[$name];
}
throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
}
/**
* Sets table style.
*
* @param TableStyle|string $name The style name or a TableStyle instance
*
* @return $this
*/
public function setStyle($name)
{
$this->style = $this->resolveStyle($name);
return $this;
}
/**
* Gets the current table style.
*
* @return TableStyle
*/
public function getStyle()
{
return $this->style;
}
/**
* Sets table column style.
*
* @param int $columnIndex Column index
* @param TableStyle|string $name The style name or a TableStyle instance
*
* @return $this
*/
public function setColumnStyle($columnIndex, $name)
{
$columnIndex = (int) $columnIndex;
$this->columnStyles[$columnIndex] = $this->resolveStyle($name);
return $this;
}
/**
* Gets the current style for a column.
*
* If style was not set, it returns the global table style.
*
* @param int $columnIndex Column index
*
* @return TableStyle
*/
public function getColumnStyle($columnIndex)
{
return $this->columnStyles[$columnIndex] ?? $this->getStyle();
}
/**
* Sets the minimum width of a column.
*
* @param int $columnIndex Column index
* @param int $width Minimum column width in characters
*
* @return $this
*/
public function setColumnWidth($columnIndex, $width)
{
$this->columnWidths[(int) $columnIndex] = (int) $width;
return $this;
}
/**
* Sets the minimum width of all columns.
*
* @return $this
*/
public function setColumnWidths(array $widths)
{
$this->columnWidths = [];
foreach ($widths as $index => $width) {
$this->setColumnWidth($index, $width);
}
return $this;
}
/**
* Sets the maximum width of a column.
*
* Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
* formatted strings are preserved.
*
* @return $this
*/
public function setColumnMaxWidth(int $columnIndex, int $width): self
{
if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, \get_class($this->output->getFormatter())));
}
$this->columnMaxWidths[$columnIndex] = $width;
return $this;
}
public function setHeaders(array $headers)
{
$headers = array_values($headers);
if (!empty($headers) && !\is_array($headers[0])) {
$headers = [$headers];
}
$this->headers = $headers;
return $this;
}
public function setRows(array $rows)
{
$this->rows = [];
return $this->addRows($rows);
}
public function addRows(array $rows)
{
foreach ($rows as $row) {
$this->addRow($row);
}
return $this;
}
public function addRow($row)
{
if ($row instanceof TableSeparator) {
$this->rows[] = $row;
return $this;
}
if (!\is_array($row)) {
throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.');
}
$this->rows[] = array_values($row);
return $this;
}
/**
* Adds a row to the table, and re-renders the table.
*/
public function appendRow($row): self
{
if (!$this->output instanceof ConsoleSectionOutput) {
throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
}
if ($this->rendered) {
$this->output->clear($this->calculateRowCount());
}
$this->addRow($row);
$this->render();
return $this;
}
public function setRow($column, array $row)
{
$this->rows[$column] = $row;
return $this;
}
public function setHeaderTitle(?string $title): self
{
$this->headerTitle = $title;
return $this;
}
public function setFooterTitle(?string $title): self
{
$this->footerTitle = $title;
return $this;
}
public function setHorizontal(bool $horizontal = true): self
{
$this->horizontal = $horizontal;
return $this;
}
/**
* Renders table to output.
*
* Example:
*
* +---------------+-----------------------+------------------+
* | ISBN | Title | Author |
* +---------------+-----------------------+------------------+
* | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
* | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
* +---------------+-----------------------+------------------+
*/
public function render()
{
$divider = new TableSeparator();
if ($this->horizontal) {
$rows = [];
foreach ($this->headers[0] ?? [] as $i => $header) {
$rows[$i] = [$header];
foreach ($this->rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
if (isset($row[$i])) {
$rows[$i][] = $row[$i];
} elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) {
// Noop, there is a "title"
} else {
$rows[$i][] = null;
}
}
}
} else {
$rows = array_merge($this->headers, [$divider], $this->rows);
}
$this->calculateNumberOfColumns($rows);
$rows = $this->buildTableRows($rows);
$this->calculateColumnsWidth($rows);
$isHeader = !$this->horizontal;
$isFirstRow = $this->horizontal;
foreach ($rows as $row) {
if ($divider === $row) {
$isHeader = false;
$isFirstRow = true;
continue;
}
if ($row instanceof TableSeparator) {
$this->renderRowSeparator();
continue;
}
if (!$row) {
continue;
}
if ($isHeader || $isFirstRow) {
if ($isFirstRow) {
$this->renderRowSeparator(self::SEPARATOR_TOP_BOTTOM);
$isFirstRow = false;
} else {
$this->renderRowSeparator(self::SEPARATOR_TOP, $this->headerTitle, $this->style->getHeaderTitleFormat());
}
}
if ($this->horizontal) {
$this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
} else {
$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
}
}
$this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
$this->cleanup();
$this->rendered = true;
}
/**
* Renders horizontal header separator.
*
* Example:
*
* +-----+-----------+-------+
*/
private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null)
{
if (0 === $count = $this->numberOfColumns) {
return;
}
$borders = $this->style->getBorderChars();
if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) {
return;
}
$crossings = $this->style->getCrossingChars();
if (self::SEPARATOR_MID === $type) {
list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
} elseif (self::SEPARATOR_TOP === $type) {
list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
} elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
} else {
list($horizontal, $leftChar, $midChar, $rightChar) = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
}
$markup = $leftChar;
for ($column = 0; $column < $count; ++$column) {
$markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
$markup .= $column === $count - 1 ? $rightChar : $midChar;
}
if (null !== $title) {
$titleLength = Helper::strlenWithoutDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title));
$markupLength = Helper::strlen($markup);
if ($titleLength > $limit = $markupLength - 4) {
$titleLength = $limit;
$formatLength = Helper::strlenWithoutDecoration($formatter, sprintf($titleFormat, ''));
$formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
}
$titleStart = ($markupLength - $titleLength) / 2;
if (false === mb_detect_encoding($markup, null, true)) {
$markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
} else {
$markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
}
}
$this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
}
/**
* Renders vertical column separator.
*/
private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string
{
$borders = $this->style->getBorderChars();
return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
}
/**
* Renders table row.
*
* Example:
*
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
*/
private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null)
{
$rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
$columns = $this->getRowColumns($row);
$last = \count($columns) - 1;
foreach ($columns as $i => $column) {
if ($firstCellFormat && 0 === $i) {
$rowContent .= $this->renderCell($row, $column, $firstCellFormat);
} else {
$rowContent .= $this->renderCell($row, $column, $cellFormat);
}
$rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
}
$this->output->writeln($rowContent);
}
/**
* Renders table cell with padding.
*/
private function renderCell(array $row, int $column, string $cellFormat): string
{
$cell = isset($row[$column]) ? $row[$column] : '';
$width = $this->effectiveColumnWidths[$column];
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
// add the width of the following columns(numbers of colspan).
foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
$width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
}
}
// str_pad won't work properly with multi-byte strings, we need to fix the padding
if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
$width += \strlen($cell) - mb_strwidth($cell, $encoding);
}
$style = $this->getColumnStyle($column);
if ($cell instanceof TableSeparator) {
return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
}
$width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
$content = sprintf($style->getCellRowContentFormat(), $cell);
return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType()));
}
/**
* Calculate number of columns for this table.
*/
private function calculateNumberOfColumns(array $rows)
{
$columns = [0];
foreach ($rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
$columns[] = $this->getNumberOfColumns($row);
}
$this->numberOfColumns = max($columns);
}
private function buildTableRows(array $rows): TableRows
{
/** @var WrappableOutputFormatterInterface $formatter */
$formatter = $this->output->getFormatter();
$unmergedRows = [];
for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
$rows = $this->fillNextRows($rows, $rowKey);
// Remove any new line breaks and replace it with a new line
foreach ($rows[$rowKey] as $column => $cell) {
$colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
if (isset($this->columnMaxWidths[$column]) && Helper::strlenWithoutDecoration($formatter, $cell) > $this->columnMaxWidths[$column]) {
$cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
}
if (!strstr($cell, "\n")) {
continue;
}
$escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell)));
$cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
foreach ($lines as $lineKey => $line) {
if ($colspan > 1) {
$line = new TableCell($line, ['colspan' => $colspan]);
}
if (0 === $lineKey) {
$rows[$rowKey][$column] = $line;
} else {
$unmergedRows[$rowKey][$lineKey][$column] = $line;
}
}
}
}
return new TableRows(function () use ($rows, $unmergedRows): \Traversable {
foreach ($rows as $rowKey => $row) {
yield $this->fillCells($row);
if (isset($unmergedRows[$rowKey])) {
foreach ($unmergedRows[$rowKey] as $row) {
yield $row;
}
}
}
});
}
private function calculateRowCount(): int
{
$numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows))));
if ($this->headers) {
++$numberOfRows; // Add row for header separator
}
++$numberOfRows; // Add row for footer separator
return $numberOfRows;
}
/**
* fill rows that contains rowspan > 1.
*
* @throws InvalidArgumentException
*/
private function fillNextRows(array $rows, int $line): array
{
$unmergedRows = [];
foreach ($rows[$line] as $column => $cell) {
if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing __toString, %s given.', \gettype($cell)));
}
if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
$nbLines = $cell->getRowspan() - 1;
$lines = [$cell];
if (strstr($cell, "\n")) {
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
$nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
$rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]);
unset($lines[0]);
}
// create a two dimensional array (rowspan x colspan)
$unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
$value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : '';
$unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan()]);
if ($nbLines === $unmergedRowKey - $line) {
break;
}
}
}
}
foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
// we need to know if $unmergedRow will be merged or inserted into $rows
if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
foreach ($unmergedRow as $cellKey => $cell) {
// insert cell into row at cellKey position
array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
}
} else {
$row = $this->copyRow($rows, $unmergedRowKey - 1);
foreach ($unmergedRow as $column => $cell) {
if (!empty($cell)) {
$row[$column] = $unmergedRow[$column];
}
}
array_splice($rows, $unmergedRowKey, 0, [$row]);
}
}
return $rows;
}
/**
* fill cells for a row that contains colspan > 1.
*/
private function fillCells($row)
{
$newRow = [];
foreach ($row as $column => $cell) {
$newRow[] = $cell;
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
// insert empty value at column position
$newRow[] = '';
}
}
}
return $newRow ?: $row;
}
private function copyRow(array $rows, int $line): array
{
$row = $rows[$line];
foreach ($row as $cellKey => $cellValue) {
$row[$cellKey] = '';
if ($cellValue instanceof TableCell) {
$row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
}
}
return $row;
}
/**
* Gets number of columns by row.
*/
private function getNumberOfColumns(array $row): int
{
$columns = \count($row);
foreach ($row as $column) {
$columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
}
return $columns;
}
/**
* Gets list of columns for the given row.
*/
private function getRowColumns(array $row): array
{
$columns = range(0, $this->numberOfColumns - 1);
foreach ($row as $cellKey => $cell) {
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
// exclude grouped columns.
$columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
}
}
return $columns;
}
/**
* Calculates columns widths.
*/
private function calculateColumnsWidth(iterable $rows)
{
for ($column = 0; $column < $this->numberOfColumns; ++$column) {
$lengths = [];
foreach ($rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
foreach ($row as $i => $cell) {
if ($cell instanceof TableCell) {
$textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
$textLength = Helper::strlen($textContent);
if ($textLength > 0) {
$contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
foreach ($contentColumns as $position => $content) {
$row[$i + $position] = $content;
}
}
}
}
$lengths[] = $this->getCellWidth($row, $column);
}
$this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2;
}
}
private function getColumnSeparatorWidth(): int
{
return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
}
private function getCellWidth(array $row, int $column): int
{
$cellWidth = 0;
if (isset($row[$column])) {
$cell = $row[$column];
$cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
}
$columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0;
$cellWidth = max($cellWidth, $columnWidth);
return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
}
/**
* Called after rendering to cleanup cache data.
*/
private function cleanup()
{
$this->effectiveColumnWidths = [];
$this->numberOfColumns = null;
}
private static function initStyles(): array
{
$borderless = new TableStyle();
$borderless
->setHorizontalBorderChars('=')
->setVerticalBorderChars(' ')
->setDefaultCrossingChar(' ')
;
$compact = new TableStyle();
$compact
->setHorizontalBorderChars('')
->setVerticalBorderChars(' ')
->setDefaultCrossingChar('')
->setCellRowContentFormat('%s')
;
$styleGuide = new TableStyle();
$styleGuide
->setHorizontalBorderChars('-')
->setVerticalBorderChars(' ')
->setDefaultCrossingChar(' ')
->setCellHeaderFormat('%s')
;
$box = (new TableStyle())
->setHorizontalBorderChars('─')
->setVerticalBorderChars('│')
->setCrossingChars('┼', '┌', '┬', 'â”<C3A2>', '┤', '┘', 'â”´', 'â””', '├')
;
$boxDouble = (new TableStyle())
->setHorizontalBorderChars('â•<C3A2>', '─')
->setVerticalBorderChars('║', '│')
->setCrossingChars('┼', 'â•”', '╤', 'â•—', 'â•¢', 'â•<C3A2>', 'â•§', '╚', '╟', 'â• ', '╪', 'â•£')
;
return [
'default' => new TableStyle(),
'borderless' => $borderless,
'compact' => $compact,
'symfony-style-guide' => $styleGuide,
'box' => $box,
'box-double' => $boxDouble,
];
}
private function resolveStyle($name): TableStyle
{
if ($name instanceof TableStyle) {
return $name;
}
if (isset(self::$styles[$name])) {
return self::$styles[$name];
}
throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
class ProgressIndicator
{
private $output;
private $startTime;
private $format;
private $message;
private $indicatorValues;
private $indicatorCurrent;
private $indicatorChangeInterval;
private $indicatorUpdateTime;
private $started = false;
private static $formatters;
private static $formats;
/**
* @param string|null $format Indicator format
* @param int $indicatorChangeInterval Change interval in milliseconds
* @param array|null $indicatorValues Animated indicator characters
*/
public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null)
{
$this->output = $output;
if (null === $format) {
$format = $this->determineBestFormat();
}
if (null === $indicatorValues) {
$indicatorValues = ['-', '\\', '|', '/'];
}
$indicatorValues = array_values($indicatorValues);
if (2 > \count($indicatorValues)) {
throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
}
$this->format = self::getFormatDefinition($format);
$this->indicatorChangeInterval = $indicatorChangeInterval;
$this->indicatorValues = $indicatorValues;
$this->startTime = time();
}
/**
* Sets the current indicator message.
*
* @param string|null $message
*/
public function setMessage($message)
{
$this->message = $message;
$this->display();
}
/**
* Starts the indicator output.
*
* @param $message
*/
public function start($message)
{
if ($this->started) {
throw new LogicException('Progress indicator already started.');
}
$this->message = $message;
$this->started = true;
$this->startTime = time();
$this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
$this->indicatorCurrent = 0;
$this->display();
}
/**
* Advances the indicator.
*/
public function advance()
{
if (!$this->started) {
throw new LogicException('Progress indicator has not yet been started.');
}
if (!$this->output->isDecorated()) {
return;
}
$currentTime = $this->getCurrentTimeInMilliseconds();
if ($currentTime < $this->indicatorUpdateTime) {
return;
}
$this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
++$this->indicatorCurrent;
$this->display();
}
/**
* Finish the indicator with message.
*
* @param $message
*/
public function finish($message)
{
if (!$this->started) {
throw new LogicException('Progress indicator has not yet been started.');
}
$this->message = $message;
$this->display();
$this->output->writeln('');
$this->started = false;
}
/**
* Gets the format for a given name.
*
* @param string $name The format name
*
* @return string|null A format string
*/
public static function getFormatDefinition($name)
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
return isset(self::$formats[$name]) ? self::$formats[$name] : null;
}
/**
* Sets a placeholder formatter for a given name.
*
* This method also allow you to override an existing placeholder.
*
* @param string $name The placeholder name (including the delimiter char like %)
* @param callable $callable A PHP callable
*/
public static function setPlaceholderFormatterDefinition($name, $callable)
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters[$name] = $callable;
}
/**
* Gets the placeholder formatter for a given name.
*
* @param string $name The placeholder name (including the delimiter char like %)
*
* @return callable|null A PHP callable
*/
public static function getPlaceholderFormatterDefinition($name)
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
return isset(self::$formatters[$name]) ? self::$formatters[$name] : null;
}
private function display()
{
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return;
}
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
return $formatter($this);
}
return $matches[0];
}, $this->format));
}
private function determineBestFormat(): string
{
switch ($this->output->getVerbosity()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
case OutputInterface::VERBOSITY_VERBOSE:
return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi';
case OutputInterface::VERBOSITY_VERY_VERBOSE:
case OutputInterface::VERBOSITY_DEBUG:
return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi';
default:
return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi';
}
}
/**
* Overwrites a previous message to the output.
*/
private function overwrite(string $message)
{
if ($this->output->isDecorated()) {
$this->output->write("\x0D\x1B[2K");
$this->output->write($message);
} else {
$this->output->writeln($message);
}
}
private function getCurrentTimeInMilliseconds(): float
{
return round(microtime(true) * 1000);
}
private static function initPlaceholderFormatters(): array
{
return [
'indicator' => function (self $indicator) {
return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)];
},
'message' => function (self $indicator) {
return $indicator->message;
},
'elapsed' => function (self $indicator) {
return Helper::formatTime(time() - $indicator->startTime);
},
'memory' => function () {
return Helper::formatMemory(memory_get_usage(true));
},
];
}
private static function initFormats(): array
{
return [
'normal' => ' %indicator% %message%',
'normal_no_ansi' => ' %message%',
'verbose' => ' %indicator% %message% (%elapsed:6s%)',
'verbose_no_ansi' => ' %message% (%elapsed:6s%)',
'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Output\OutputInterface;
/**
* This class adds helper method to describe objects in various formats.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class DescriptorHelper extends Helper
{
/**
* @var DescriptorInterface[]
*/
private $descriptors = [];
public function __construct()
{
$this
->register('txt', new TextDescriptor())
->register('xml', new XmlDescriptor())
->register('json', new JsonDescriptor())
->register('md', new MarkdownDescriptor())
;
}
/**
* Describes an object if supported.
*
* Available options are:
* * format: string, the output format name
* * raw_text: boolean, sets output type as raw
*
* @param object $object
*
* @throws InvalidArgumentException when the given format is not supported
*/
public function describe(OutputInterface $output, $object, array $options = [])
{
$options = array_merge([
'raw_text' => false,
'format' => 'txt',
], $options);
if (!isset($this->descriptors[$options['format']])) {
throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
}
$descriptor = $this->descriptors[$options['format']];
$descriptor->describe($output, $object, $options);
}
/**
* Registers a descriptor.
*
* @param string $format
*
* @return $this
*/
public function register($format, DescriptorInterface $descriptor)
{
$this->descriptors[$format] = $descriptor;
return $this;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'descriptor';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\DependencyInjection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\TypedReference;
/**
* Registers console commands.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class AddConsoleCommandPass implements CompilerPassInterface
{
private $commandLoaderServiceId;
private $commandTag;
public function __construct(string $commandLoaderServiceId = 'console.command_loader', string $commandTag = 'console.command')
{
$this->commandLoaderServiceId = $commandLoaderServiceId;
$this->commandTag = $commandTag;
}
public function process(ContainerBuilder $container)
{
$commandServices = $container->findTaggedServiceIds($this->commandTag, true);
$lazyCommandMap = [];
$lazyCommandRefs = [];
$serviceIds = [];
foreach ($commandServices as $id => $tags) {
$definition = $container->getDefinition($id);
$class = $container->getParameterBag()->resolveValue($definition->getClass());
if (isset($tags[0]['command'])) {
$commandName = $tags[0]['command'];
} else {
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(Command::class)) {
throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
}
$commandName = $class::getDefaultName();
}
if (null === $commandName) {
if (!$definition->isPublic() || $definition->isPrivate()) {
$commandId = 'console.command.public_alias.'.$id;
$container->setAlias($commandId, $id)->setPublic(true);
$id = $commandId;
}
$serviceIds[] = $id;
continue;
}
unset($tags[0]);
$lazyCommandMap[$commandName] = $id;
$lazyCommandRefs[$id] = new TypedReference($id, $class);
$aliases = [];
foreach ($tags as $tag) {
if (isset($tag['command'])) {
$aliases[] = $tag['command'];
$lazyCommandMap[$tag['command']] = $id;
}
}
$definition->addMethodCall('setName', [$commandName]);
if ($aliases) {
$definition->addMethodCall('setAliases', [$aliases]);
}
}
$container
->register($this->commandLoaderServiceId, ContainerCommandLoader::class)
->setPublic(true)
->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]);
$container->setParameter('console.command.ids', $serviceIds);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents an incorrect option name typed in the console.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* ExceptionInterface.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents an incorrect namespace typed in the console.
*
* @author Pierre du Plessis <pdples@gmail.com>
*/
class NamespaceNotFoundException extends CommandNotFoundException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents an incorrect command name typed in the console.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
private $alternatives;
/**
* @param string $message Exception message to throw
* @param array $alternatives List of similar defined names
* @param int $code Exception code
* @param \Throwable $previous Previous exception used for the exception chaining
*/
public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->alternatives = $alternatives;
}
/**
* @return array A list of similar defined names
*/
public function getAlternatives()
{
return $this->alternatives;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Allows to manipulate the exit code of a command after its execution.
*
* @author Francesco Levorato <git@flevour.net>
*
* @final since Symfony 4.4
*/
class ConsoleTerminateEvent extends ConsoleEvent
{
private $exitCode;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $exitCode)
{
parent::__construct($command, $input, $output);
$this->setExitCode($exitCode);
}
/**
* Sets the exit code.
*
* @param int $exitCode The command exit code
*/
public function setExitCode($exitCode)
{
$this->exitCode = (int) $exitCode;
}
/**
* Gets the exit code.
*
* @return int The command exit code
*/
public function getExitCode()
{
return $this->exitCode;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Allows to handle throwables thrown while running a command.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
final class ConsoleErrorEvent extends ConsoleEvent
{
private $error;
private $exitCode;
public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null)
{
parent::__construct($command, $input, $output);
$this->error = $error;
}
public function getError(): \Throwable
{
return $this->error;
}
public function setError(\Throwable $error): void
{
$this->error = $error;
}
public function setExitCode(int $exitCode): void
{
$this->exitCode = $exitCode;
$r = new \ReflectionProperty($this->error, 'code');
$r->setAccessible(true);
$r->setValue($this->error, $this->exitCode);
}
public function getExitCode(): int
{
return null !== $this->exitCode ? $this->exitCode : (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
/**
* Allows to do things before the command is executed, like skipping the command or changing the input.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final since Symfony 4.4
*/
class ConsoleCommandEvent extends ConsoleEvent
{
/**
* The return code for skipped commands, this will also be passed into the terminate event.
*/
const RETURN_CODE_DISABLED = 113;
/**
* Indicates if the command should be run or skipped.
*/
private $commandShouldRun = true;
/**
* Disables the command, so it won't be run.
*
* @return bool
*/
public function disableCommand()
{
return $this->commandShouldRun = false;
}
/**
* Enables the command.
*
* @return bool
*/
public function enableCommand()
{
return $this->commandShouldRun = true;
}
/**
* Returns true if the command is runnable, false otherwise.
*
* @return bool
*/
public function commandShouldRun()
{
return $this->commandShouldRun;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\Event;
/**
* Allows to inspect input and output of a command.
*
* @author Francesco Levorato <git@flevour.net>
*/
class ConsoleEvent extends Event
{
protected $command;
private $input;
private $output;
public function __construct(Command $command = null, InputInterface $input, OutputInterface $output)
{
$this->command = $command;
$this->input = $input;
$this->output = $output;
}
/**
* Gets the command that is executed.
*
* @return Command|null A Command instance
*/
public function getCommand()
{
return $this->command;
}
/**
* Gets the input instance.
*
* @return InputInterface An InputInterface instance
*/
public function getInput()
{
return $this->input;
}
/**
* Gets the output instance.
*
* @return OutputInterface An OutputInterface instance
*/
public function getOutput()
{
return $this->output;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* @author James Halsall <james.t.halsall@googlemail.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class ErrorListener implements EventSubscriberInterface
{
private $logger;
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
}
public function onConsoleError(ConsoleErrorEvent $event)
{
if (null === $this->logger) {
return;
}
$error = $event->getError();
if (!$inputString = $this->getInputString($event)) {
$this->logger->error('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]);
return;
}
$this->logger->error('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
}
public function onConsoleTerminate(ConsoleTerminateEvent $event)
{
if (null === $this->logger) {
return;
}
$exitCode = $event->getExitCode();
if (0 === $exitCode) {
return;
}
if (!$inputString = $this->getInputString($event)) {
$this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]);
return;
}
$this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]);
}
public static function getSubscribedEvents()
{
return [
ConsoleEvents::ERROR => ['onConsoleError', -128],
ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128],
];
}
private static function getInputString(ConsoleEvent $event): ?string
{
$commandName = $event->getCommand() ? $event->getCommand()->getName() : null;
$input = $event->getInput();
if (method_exists($input, '__toString')) {
if ($commandName) {
return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input);
}
return (string) $input;
}
return $commandName;
}
}
<?php
return array(
'A' => 'a',
'B' => 'b',
'C' => 'c',
'D' => 'd',
'E' => 'e',
'F' => 'f',
'G' => 'g',
'H' => 'h',
'I' => 'i',
'J' => 'j',
'K' => 'k',
'L' => 'l',
'M' => 'm',
'N' => 'n',
'O' => 'o',
'P' => 'p',
'Q' => 'q',
'R' => 'r',
'S' => 's',
'T' => 't',
'U' => 'u',
'V' => 'v',
'W' => 'w',
'X' => 'x',
'Y' => 'y',
'Z' => 'z',
'À' => 'à',
<>' => 'á',
'Â' => 'â',
'Ã' => 'ã',
'Ä' => 'ä',
'Ã…' => 'Ã¥',
'Æ' => 'æ',
'Ç' => 'ç',
'È' => 'è',
'É' => 'é',
'Ê' => 'ê',
'Ë' => 'ë',
'Ì' => 'ì',
<>' => 'í',
'Î' => 'î',
<>' => 'ï',
<>' => 'ð',
'Ñ' => 'ñ',
'Ò' => 'ò',
'Ó' => 'ó',
'Ô' => 'ô',
'Õ' => 'õ',
'Ö' => 'ö',
'Ø' => 'ø',
'Ù' => 'ù',
'Ú' => 'ú',
'Û' => 'û',
'Ü' => 'ü',
<>' => 'ý',
'Þ' => 'þ',
'Ä€' => 'Ä<>',
'Ă' => 'ă',
'Ä„' => 'Ä…',
'Ć' => 'ć',
'Ĉ' => 'ĉ',
'ÄŠ' => 'Ä‹',
'ÄŒ' => 'Ä<>',
'ÄŽ' => 'Ä<>',
<>' => 'Ä‘',
'Ä’' => 'Ä“',
'Ä”' => 'Ä•',
'Ä–' => 'Ä—',
'Ę' => 'ę',
'Äš' => 'Ä›',
'Äœ' => 'Ä<>',
'Äž' => 'ÄŸ',
'Ä ' => 'Ä¡',
'Ä¢' => 'Ä£',
'Ĥ' => 'ĥ',
'Ħ' => 'ħ',
'Ĩ' => 'ĩ',
'Ī' => 'ī',
'Ĭ' => 'ĭ',
'Į' => 'į',
'İ' => 'i',
'IJ' => 'ij',
'Ĵ' => 'ĵ',
'Ķ' => 'ķ',
'Ĺ' => 'ĺ',
'Ļ' => 'ļ',
'Ľ' => 'ľ',
'Ä¿' => 'Å€',
<>' => 'Å‚',
'Ń' => 'ń',
'Ņ' => 'ņ',
'Ň' => 'ň',
'ÅŠ' => 'Å‹',
'ÅŒ' => 'Å<>',
'ÅŽ' => 'Å<>',
<>' => 'Å‘',
'Å’' => 'Å“',
'Å”' => 'Å•',
'Å–' => 'Å—',
'Ř' => 'ř',
'Åš' => 'Å›',
'Åœ' => 'Å<>',
'Åž' => 'ÅŸ',
'Å ' => 'Å¡',
'Å¢' => 'Å£',
'Ť' => 'ť',
'Ŧ' => 'ŧ',
'Ũ' => 'ũ',
'Ū' => 'ū',
'Ŭ' => 'ŭ',
'Ů' => 'ů',
'Ű' => 'ű',
'Ų' => 'ų',
'Ŵ' => 'ŵ',
'Ŷ' => 'ŷ',
'Ÿ' => 'ÿ',
'Ź' => 'ź',
'Ż' => 'ż',
'Ž' => 'ž',
<>' => 'É“',
'Ƃ' => 'ƃ',
'Æ„' => 'Æ…',
'Ɔ' => 'ɔ',
'Ƈ' => 'ƈ',
'Ɖ' => 'ɖ',
'ÆŠ' => 'É—',
'Ƌ' => 'ƌ',
'ÆŽ' => 'Ç<>',
<>' => 'É™',
<>' => 'É›',
'Æ‘' => 'Æ’',
'Æ“' => 'É ',
'Æ”' => 'É£',
'Æ–' => 'É©',
'Ɨ' => 'ɨ',
'Ƙ' => 'ƙ',
'Ɯ' => 'ɯ',
<>' => 'ɲ',
'Ɵ' => 'ɵ',
'Æ ' => 'Æ¡',
'Æ¢' => 'Æ£',
'Ƥ' => 'ƥ',
'Ʀ' => 'ʀ',
'Ƨ' => 'ƨ',
'Ʃ' => 'ʃ',
'Ƭ' => 'ƭ',
'Ʈ' => 'ʈ',
'Ư' => 'ư',
'Ʊ' => 'ʊ',
'Ʋ' => 'ʋ',
'Ƴ' => 'ƴ',
'Ƶ' => 'ƶ',
'Æ·' => 'Ê’',
'Ƹ' => 'ƹ',
'Ƽ' => 'ƽ',
'DŽ' => 'dž',
'Dž' => 'dž',
'LJ' => 'lj',
'Lj' => 'lj',
'NJ' => 'nj',
'Nj' => 'nj',
<>' => 'ÇŽ',
<>' => 'Ç<>',
'Ç‘' => 'Ç’',
'Ç“' => 'Ç”',
'Ç•' => 'Ç–',
'Ǘ' => 'ǘ',
'Ç™' => 'Çš',
'Ǜ' => 'ǜ',
'Çž' => 'ÇŸ',
'Ç ' => 'Ç¡',
'Ç¢' => 'Ç£',
'Ǥ' => 'ǥ',
'Ǧ' => 'ǧ',
'Ǩ' => 'ǩ',
'Ǫ' => 'ǫ',
'Ǭ' => 'ǭ',
'Ǯ' => 'ǯ',
'DZ' => 'dz',
'Dz' => 'dz',
'Ǵ' => 'ǵ',
'Ƕ' => 'ƕ',
'Ç·' => 'Æ¿',
'Ǹ' => 'ǹ',
'Ǻ' => 'ǻ',
'Ǽ' => 'ǽ',
'Ǿ' => 'ǿ',
'È€' => 'È<>',
'Ȃ' => 'ȃ',
'È„' => 'È…',
'Ȇ' => 'ȇ',
'Ȉ' => 'ȉ',
'ÈŠ' => 'È‹',
'ÈŒ' => 'È<>',
'ÈŽ' => 'È<>',
<>' => 'È‘',
'È’' => 'È“',
'È”' => 'È•',
'È–' => 'È—',
'Ș' => 'ș',
'Èš' => 'È›',
'Èœ' => 'È<>',
'Èž' => 'ÈŸ',
'È ' => 'Æž',
'È¢' => 'È£',
'Ȥ' => 'ȥ',
'Ȧ' => 'ȧ',
'Ȩ' => 'ȩ',
'Ȫ' => 'ȫ',
'Ȭ' => 'ȭ',
'Ȯ' => 'ȯ',
'Ȱ' => 'ȱ',
'Ȳ' => 'ȳ',
'Ⱥ' => 'ⱥ',
'Ȼ' => 'ȼ',
'Ƚ' => 'ƚ',
'Ⱦ' => 'ⱦ',
<>' => 'É‚',
'Ƀ' => 'ƀ',
'Ʉ' => 'ʉ',
'Ʌ' => 'ʌ',
'Ɇ' => 'ɇ',
'Ɉ' => 'ɉ',
'ÉŠ' => 'É‹',
'ÉŒ' => 'É<>',
'ÉŽ' => 'É<>',
'Ͱ' => 'ͱ',
'Ͳ' => 'ͳ',
'Ͷ' => 'ͷ',
'Ϳ' => 'ϳ',
'Ά' => 'ά',
'Έ' => 'έ',
'Ή' => 'ή',
'Ί' => 'ί',
'Ό' => 'ό',
'ÎŽ' => 'Ï<>',
<>' => 'ÏŽ',
'Α' => 'α',
'Β' => 'β',
'Γ' => 'γ',
'Δ' => 'δ',
'Ε' => 'ε',
'Ζ' => 'ζ',
'Η' => 'η',
'Θ' => 'θ',
'Ι' => 'ι',
'Κ' => 'κ',
'Λ' => 'λ',
'Μ' => 'μ',
<>' => 'ν',
'Ξ' => 'ξ',
'Ο' => 'ο',
'Π' => 'π',
'Ρ' => 'Ï<>',
'Σ' => 'σ',
'Τ' => 'τ',
'Î¥' => 'Ï…',
'Φ' => 'φ',
'Χ' => 'χ',
'Ψ' => 'ψ',
'Ω' => 'ω',
'Ϊ' => 'ϊ',
'Ϋ' => 'ϋ',
<>' => 'Ï—',
'Ϙ' => 'ϙ',
'Ïš' => 'Ï›',
'Ïœ' => 'Ï<>',
'Ïž' => 'ÏŸ',
'Ï ' => 'Ï¡',
'Ï¢' => 'Ï£',
'Ϥ' => 'ϥ',
'Ϧ' => 'ϧ',
'Ϩ' => 'ϩ',
'Ϫ' => 'ϫ',
'Ϭ' => 'ϭ',
'Ϯ' => 'ϯ',
'ϴ' => 'θ',
'Ϸ' => 'ϸ',
'Ϲ' => 'ϲ',
'Ϻ' => 'ϻ',
'Ͻ' => 'ͻ',
'Ͼ' => 'ͼ',
'Ͽ' => 'ͽ',
'Ѐ' => 'Ñ<>',
<>' => 'Ñ‘',
'Ђ' => 'ђ',
'Ѓ' => 'ѓ',
'Є' => 'є',
'Ð…' => 'Ñ•',
'І' => 'і',
'Ї' => 'ї',
'Ј' => 'ј',
'Љ' => 'љ',
'Њ' => 'њ',
'Ћ' => 'ћ',
'Ќ' => 'ќ',
<>' => 'Ñ<>',
'ÐŽ' => 'Ñž',
<>' => 'ÑŸ',
<>' => 'а',
'Б' => 'б',
'В' => 'в',
'Г' => 'г',
'Д' => 'д',
'Е' => 'е',
'Ж' => 'ж',
'З' => 'з',
'И' => 'и',
'Й' => 'й',
'К' => 'к',
'Л' => 'л',
'М' => 'м',
<>' => 'н',
'О' => 'о',
'П' => 'п',
'Р' => 'р',
'С' => 'Ñ<>',
'Т' => 'т',
'У' => 'у',
'Ф' => 'ф',
'Ð¥' => 'Ñ…',
'Ц' => 'ц',
'Ч' => 'ч',
'Ш' => 'ш',
'Щ' => 'щ',
'Ъ' => 'ъ',
'Ы' => 'ы',
'Ь' => 'ь',
'Э' => 'Ñ<>',
'Ю' => 'ю',
'Я' => 'Ñ<>',
'Ñ ' => 'Ñ¡',
'Ñ¢' => 'Ñ£',
'Ѥ' => 'ѥ',
'Ѧ' => 'ѧ',
'Ѩ' => 'ѩ',
'Ѫ' => 'ѫ',
'Ѭ' => 'ѭ',
'Ѯ' => 'ѯ',
'Ѱ' => 'ѱ',
'Ѳ' => 'ѳ',
'Ѵ' => 'ѵ',
'Ѷ' => 'ѷ',
'Ѹ' => 'ѹ',
'Ѻ' => 'ѻ',
'Ѽ' => 'ѽ',
'Ѿ' => 'ѿ',
'Ò€' => 'Ò<>',
'ÒŠ' => 'Ò‹',
'ÒŒ' => 'Ò<>',
'ÒŽ' => 'Ò<>',
<>' => 'Ò‘',
'Ò’' => 'Ò“',
'Ò”' => 'Ò•',
'Ò–' => 'Ò—',
'Ò˜' => 'Ò™',
'Òš' => 'Ò›',
'Òœ' => 'Ò<>',
'Òž' => 'ÒŸ',
'Ò ' => 'Ò¡',
'Ò¢' => 'Ò£',
'Ò¤' => 'Ò¥',
'Ò¦' => 'Ò§',
'Ò¨' => 'Ò©',
'Òª' => 'Ò«',
'Ò¬' => 'Ò­',
'Ò®' => 'Ò¯',
'Ò°' => 'Ò±',
'Ò²' => 'Ò³',
'Ò´' => 'Òµ',
'Ò¶' => 'Ò·',
'Ò¸' => 'Ò¹',
'Òº' => 'Ò»',
'Ò¼' => 'Ò½',
'Ò¾' => 'Ò¿',
'Ó€' => 'Ó<>',
<>' => 'Ó‚',
'Óƒ' => 'Ó„',
'Ó…' => 'Ó†',
'Ó‡' => 'Óˆ',
'Ó‰' => 'ÓŠ',
'Ӌ' => 'ӌ',
<>' => 'ÓŽ',
<>' => 'Ó‘',
'Ó’' => 'Ó“',
'Ó”' => 'Ó•',
'Ó–' => 'Ó—',
'Ó˜' => 'Ó™',
'Óš' => 'Ó›',
'Óœ' => 'Ó<>',
'Óž' => 'ÓŸ',
'Ó ' => 'Ó¡',
'Ó¢' => 'Ó£',
'Ó¤' => 'Ó¥',
'Ó¦' => 'Ó§',
'Ó¨' => 'Ó©',
'Óª' => 'Ó«',
'Ó¬' => 'Ó­',
'Ó®' => 'Ó¯',
'Ó°' => 'Ó±',
'Ó²' => 'Ó³',
'Ó´' => 'Óµ',
'Ó¶' => 'Ó·',
'Ó¸' => 'Ó¹',
'Óº' => 'Ó»',
'Ó¼' => 'Ó½',
'Ó¾' => 'Ó¿',
'Ô€' => 'Ô<>',
'Ô‚' => 'Ôƒ',
'Ô„' => 'Ô…',
'Ô†' => 'Ô‡',
'Ôˆ' => 'Ô‰',
'ÔŠ' => 'Ô‹',
'ÔŒ' => 'Ô<>',
'ÔŽ' => 'Ô<>',
<>' => 'Ô‘',
'Ô’' => 'Ô“',
'Ô”' => 'Ô•',
'Ô–' => 'Ô—',
'Ô˜' => 'Ô™',
'Ôš' => 'Ô›',
'Ôœ' => 'Ô<>',
'Ôž' => 'ÔŸ',
'Ô ' => 'Ô¡',
'Ô¢' => 'Ô£',
'Ô¤' => 'Ô¥',
'Ô¦' => 'Ô§',
'Ô¨' => 'Ô©',
'Ôª' => 'Ô«',
'Ô¬' => 'Ô­',
'Ô®' => 'Ô¯',
'Ô±' => 'Õ¡',
'Ô²' => 'Õ¢',
'Ô³' => 'Õ£',
'Ô´' => 'Õ¤',
'Ôµ' => 'Õ¥',
'Ô¶' => 'Õ¦',
'Ô·' => 'Õ§',
'Ô¸' => 'Õ¨',
'Ô¹' => 'Õ©',
'Ôº' => 'Õª',
'Ô»' => 'Õ«',
'Ô¼' => 'Õ¬',
'Ô½' => 'Õ­',
'Ô¾' => 'Õ®',
'Ô¿' => 'Õ¯',
'Õ€' => 'Õ°',
<>' => 'Õ±',
'Õ‚' => 'Õ²',
'Õƒ' => 'Õ³',
'Õ„' => 'Õ´',
'Õ…' => 'Õµ',
'Õ†' => 'Õ¶',
'Õ‡' => 'Õ·',
'Õˆ' => 'Õ¸',
'Õ‰' => 'Õ¹',
'ÕŠ' => 'Õº',
'Õ‹' => 'Õ»',
'Ռ' => 'ռ',
<>' => 'Õ½',
'ÕŽ' => 'Õ¾',
<>' => 'Õ¿',
<>' => 'Ö€',
'Õ‘' => 'Ö<>',
'Õ’' => 'Ö‚',
'Õ“' => 'Öƒ',
'Õ”' => 'Ö„',
'Õ•' => 'Ö…',
'Õ–' => 'Ö†',
'á‚ ' => 'â´€',
'á‚¡' => 'â´<C3A2>',
'á‚¢' => 'â´‚',
'á‚£' => 'â´ƒ',
'Ⴄ' => 'ⴄ',
'á‚¥' => 'â´…',
'Ⴆ' => 'ⴆ',
'á‚§' => 'â´‡',
'Ⴈ' => 'ⴈ',
'á‚©' => 'â´‰',
'Ⴊ' => 'ⴊ',
'á‚«' => 'â´‹',
'Ⴌ' => 'ⴌ',
'á‚­' => 'â´<C3A2>',
'á‚®' => 'â´Ž',
'Ⴏ' => 'â´<C3A2>',
'á‚°' => 'â´<C3A2>',
'Ⴑ' => 'ⴑ',
'Ⴒ' => 'ⴒ',
'Ⴓ' => 'ⴓ',
'á‚´' => 'â´”',
'Ⴕ' => 'ⴕ',
'á‚¶' => 'â´–',
'á‚·' => 'â´—',
'Ⴘ' => 'ⴘ',
'Ⴙ' => 'ⴙ',
'Ⴚ' => 'ⴚ',
'á‚»' => 'â´›',
'Ⴜ' => 'ⴜ',
'Ⴝ' => 'â´<C3A2>',
'Ⴞ' => 'ⴞ',
'á‚¿' => 'â´Ÿ',
'Ⴠ' => 'ⴠ',
'áƒ<C3A1>' => 'â´¡',
'Ⴢ' => 'ⴢ',
'Ⴣ' => 'ⴣ',
'Ⴤ' => 'ⴤ',
'Ⴥ' => 'ⴥ',
'Ⴧ' => 'ⴧ',
'áƒ<C3A1>' => 'â´­',
'Ḁ' => 'á¸<C3A1>',
'Ḃ' => 'ḃ',
'Ḅ' => 'ḅ',
'Ḇ' => 'ḇ',
'Ḉ' => 'ḉ',
'Ḋ' => 'ḋ',
'Ḍ' => 'á¸<C3A1>',
'Ḏ' => 'á¸<C3A1>',
¸<C3A1>' => 'ḑ',
'Ḓ' => 'ḓ',
'Ḕ' => 'ḕ',
'Ḗ' => 'ḗ',
'Ḙ' => 'ḙ',
'Ḛ' => 'ḛ',
'Ḝ' => 'á¸<C3A1>',
'Ḟ' => 'ḟ',
'Ḡ' => 'ḡ',
'Ḣ' => 'ḣ',
'Ḥ' => 'ḥ',
'Ḧ' => 'ḧ',
'Ḩ' => 'ḩ',
'Ḫ' => 'ḫ',
'Ḭ' => 'ḭ',
'Ḯ' => 'ḯ',
'Ḱ' => 'ḱ',
'Ḳ' => 'ḳ',
'Ḵ' => 'ḵ',
'Ḷ' => 'ḷ',
'Ḹ' => 'ḹ',
'Ḻ' => 'ḻ',
'Ḽ' => 'ḽ',
'Ḿ' => 'ḿ',
'á¹€' => 'á¹<C3A1>',
'Ṃ' => 'ṃ',
'Ṅ' => 'ṅ',
'Ṇ' => 'ṇ',
'Ṉ' => 'ṉ',
'Ṋ' => 'ṋ',
'Ṍ' => 'á¹<C3A1>',
'Ṏ' => 'á¹<C3A1>',
'á¹<C3A1>' => 'ṑ',
'Ṓ' => 'ṓ',
'Ṕ' => 'ṕ',
'á¹–' => 'á¹—',
'Ṙ' => 'ṙ',
'Ṛ' => 'ṛ',
'Ṝ' => 'á¹<C3A1>',
'Ṟ' => 'ṟ',
'Ṡ' => 'ṡ',
'á¹¢' => 'á¹£',
'Ṥ' => 'ṥ',
'Ṧ' => 'ṧ',
'Ṩ' => 'ṩ',
'Ṫ' => 'ṫ',
'Ṭ' => 'ṭ',
'Ṯ' => 'ṯ',
'á¹°' => 'á¹±',
'á¹²' => 'á¹³',
'á¹´' => 'á¹µ',
'á¹¶' => 'á¹·',
'Ṹ' => 'ṹ',
'Ṻ' => 'ṻ',
'á¹¼' => 'á¹½',
'Ṿ' => 'ṿ',
'Ẁ' => 'áº<C3A1>',
'Ẃ' => 'ẃ',
'Ẅ' => 'ẅ',
'Ẇ' => 'ẇ',
'Ẉ' => 'ẉ',
'Ẋ' => 'ẋ',
'Ẍ' => 'áº<C3A1>',
'Ẏ' => 'áº<C3A1>',
'áº<C3A1>' => 'ẑ',
'Ẓ' => 'ẓ',
'Ẕ' => 'ẕ',
'ẞ' => 'ß',
'Ạ' => 'ạ',
'Ả' => 'ả',
'Ấ' => 'ấ',
'Ầ' => 'ầ',
'Ẩ' => 'ẩ',
'Ẫ' => 'ẫ',
'Ậ' => 'ậ',
'Ắ' => 'ắ',
'Ằ' => 'ằ',
'Ẳ' => 'ẳ',
'Ẵ' => 'ẵ',
'Ặ' => 'ặ',
'Ẹ' => 'ẹ',
'Ẻ' => 'ẻ',
'Ẽ' => 'ẽ',
'Ế' => 'ế',
'Ề' => 'á»<C3A1>',
'Ể' => 'ể',
'Ễ' => 'ễ',
'Ệ' => 'ệ',
'Ỉ' => 'ỉ',
'Ị' => 'ị',
'Ọ' => 'á»<C3A1>',
'Ỏ' => 'á»<C3A1>',
'á»<C3A1>' => 'ố',
'Ồ' => 'ồ',
'Ổ' => 'ổ',
'á»–' => 'á»—',
'Ộ' => 'ộ',
'Ớ' => 'ớ',
'Ờ' => 'á»<C3A1>',
'Ở' => 'ở',
'Ỡ' => 'ỡ',
'Ợ' => 'ợ',
'Ụ' => 'ụ',
'Ủ' => 'ủ',
'Ứ' => 'ứ',
'Ừ' => 'ừ',
'Ử' => 'ử',
'Ữ' => 'ữ',
'á»°' => 'á»±',
'Ỳ' => 'ỳ',
'Ỵ' => 'ỵ',
'á»¶' => 'á»·',
'Ỹ' => 'ỹ',
'Ỻ' => 'ỻ',
'Ỽ' => 'ỽ',
'Ỿ' => 'ỿ',
'Ἀ' => 'ἀ',
'Ἁ' => 'á¼<C3A1>',
'Ἂ' => 'ἂ',
'Ἃ' => 'ἃ',
'Ἄ' => 'ἄ',
'á¼<C3A1>' => 'á¼…',
'Ἆ' => 'ἆ',
'á¼<C3A1>' => 'ἇ',
'Ἐ' => 'á¼<C3A1>',
'Ἑ' => 'ἑ',
'Ἒ' => 'ἒ',
'Ἓ' => 'ἓ',
'Ἔ' => 'ἔ',
'á¼<C3A1>' => 'ἕ',
'Ἠ' => 'ἠ',
'Ἡ' => 'ἡ',
'Ἢ' => 'ἢ',
'Ἣ' => 'ἣ',
'Ἤ' => 'ἤ',
'á¼­' => 'á¼¥',
'Ἦ' => 'ἦ',
'Ἧ' => 'ἧ',
'Ἰ' => 'ἰ',
'á¼¹' => 'á¼±',
'Ἲ' => 'ἲ',
'á¼»' => 'á¼³',
'á¼¼' => 'á¼´',
'á¼½' => 'á¼µ',
'á¼¾' => 'á¼¶',
'Ἷ' => 'ἷ',
'Ὀ' => 'ὀ',
'Ὁ' => 'á½<C3A1>',
'Ὂ' => 'ὂ',
'Ὃ' => 'ὃ',
'Ὄ' => 'ὄ',
'á½<C3A1>' => 'á½…',
'Ὑ' => 'ὑ',
'Ὓ' => 'ὓ',
'á½<C3A1>' => 'ὕ',
'Ὗ' => 'ὗ',
'Ὠ' => 'ὠ',
'Ὡ' => 'ὡ',
'Ὢ' => 'ὢ',
'Ὣ' => 'ὣ',
'Ὤ' => 'ὤ',
'á½­' => 'á½¥',
'Ὦ' => 'ὦ',
'Ὧ' => 'ὧ',
'ᾈ' => 'ᾀ',
'ᾉ' => 'á¾<C3A1>',
'ᾊ' => 'ᾂ',
'ᾋ' => 'ᾃ',
'ᾌ' => 'ᾄ',
'á¾<C3A1>' => 'á¾…',
'ᾎ' => 'ᾆ',
'á¾<C3A1>' => 'ᾇ',
'ᾘ' => 'á¾<C3A1>',
'ᾙ' => 'ᾑ',
'ᾚ' => 'ᾒ',
'ᾛ' => 'ᾓ',
'ᾜ' => 'ᾔ',
'á¾<C3A1>' => 'ᾕ',
'ᾞ' => 'ᾖ',
'ᾟ' => 'ᾗ',
'ᾨ' => 'ᾠ',
'ᾩ' => 'ᾡ',
'ᾪ' => 'ᾢ',
'ᾫ' => 'ᾣ',
'ᾬ' => 'ᾤ',
'á¾­' => 'á¾¥',
'ᾮ' => 'ᾦ',
'ᾯ' => 'ᾧ',
'Ᾰ' => 'ᾰ',
'á¾¹' => 'á¾±',
'Ὰ' => 'ὰ',
'á¾»' => 'á½±',
'á¾¼' => 'á¾³',
'Ὲ' => 'ὲ',
'Έ' => 'έ',
'Ὴ' => 'ὴ',
'á¿‹' => 'á½µ',
'ῌ' => 'ῃ',
'Ῐ' => 'á¿<C3A1>',
'á¿™' => 'á¿‘',
'Ὶ' => 'ὶ',
'á¿›' => 'á½·',
'Ῠ' => 'ῠ',
'á¿©' => 'á¿¡',
'Ὺ' => 'ὺ',
'á¿«' => 'á½»',
'Ῥ' => 'ῥ',
'Ὸ' => 'ὸ',
'Ό' => 'ό',
'Ὼ' => 'ὼ',
'á¿»' => 'á½½',
'ῼ' => 'ῳ',
'Ω' => 'ω',
'K' => 'k',
'â„«' => 'Ã¥',
'Ⅎ' => 'ⅎ',
'â… ' => 'â…°',
'â…¡' => 'â…±',
'â…¢' => 'â…²',
'â…£' => 'â…³',
'â…¤' => 'â…´',
'â…¥' => 'â…µ',
'â…¦' => 'â…¶',
'â…§' => 'â…·',
'â…¨' => 'â…¸',
'â…©' => 'â…¹',
'â…ª' => 'â…º',
'â…«' => 'â…»',
'â…¬' => 'â…¼',
'â…­' => 'â…½',
'â…®' => 'â…¾',
'â…¯' => 'â…¿',
'Ↄ' => 'ↄ',
'â’¶' => 'â“<C3A2>',
'â’·' => 'â“‘',
'â’¸' => 'â“’',
'â’¹' => 'â““',
'â’º' => 'â“”',
'â’»' => 'â“•',
'â’¼' => 'â“–',
'â’½' => 'â“—',
'Ⓘ' => 'ⓘ',
'â’¿' => 'â“™',
'Ⓚ' => 'ⓚ',
'â“<C3A2>' => 'â“›',
'Ⓜ' => 'ⓜ',
'Ⓝ' => 'â“<C3A2>',
'Ⓞ' => 'ⓞ',
'Ⓟ' => 'ⓟ',
'Ⓠ' => 'ⓠ',
'Ⓡ' => 'ⓡ',
'Ⓢ' => 'ⓢ',
'Ⓣ' => 'ⓣ',
'Ⓤ' => 'ⓤ',
'â“‹' => 'â“¥',
'Ⓦ' => 'ⓦ',
'â“<C3A2>' => 'â“§',
'Ⓨ' => 'ⓨ',
'â“<C3A2>' => 'â“©',
'â°€' => 'â°°',
'â°<C3A2>' => 'â°±',
'â°‚' => 'â°²',
'â°ƒ' => 'â°³',
'â°„' => 'â°´',
'â°…' => 'â°µ',
'â°†' => 'â°¶',
'â°‡' => 'â°·',
'â°ˆ' => 'â°¸',
'â°‰' => 'â°¹',
'â°Š' => 'â°º',
'â°‹' => 'â°»',
'Ⰼ' => 'ⰼ',
'â°<C3A2>' => 'â°½',
'â°Ž' => 'â°¾',
'â°<C3A2>' => 'â°¿',
'â°<C3A2>' => 'â±€',
'â°‘' => 'â±<C3A2>',
'Ⱂ' => 'ⱂ',
'Ⱃ' => 'ⱃ',
'Ⱄ' => 'ⱄ',
'â°•' => 'â±…',
'Ⱆ' => 'ⱆ',
'Ⱇ' => 'ⱇ',
'Ⱈ' => 'ⱈ',
'Ⱉ' => 'ⱉ',
'Ⱊ' => 'ⱊ',
'Ⱋ' => 'ⱋ',
'Ⱌ' => 'ⱌ',
'â°<C3A2>' => 'â±<C3A2>',
'Ⱎ' => 'ⱎ',
'â°Ÿ' => 'â±<C3A2>',
'â° ' => 'â±<C3A2>',
'Ⱑ' => 'ⱑ',
'â°¢' => 'â±’',
'Ⱓ' => 'ⱓ',
'â°¤' => 'â±”',
'Ⱕ' => 'ⱕ',
'â°¦' => 'â±–',
'â°§' => 'â±—',
'Ⱘ' => 'ⱘ',
'â°©' => 'â±™',
'Ⱚ' => 'ⱚ',
'â°«' => 'â±›',
'Ⱜ' => 'ⱜ',
'â°­' => 'â±<C3A2>',
'Ⱞ' => 'ⱞ',
'Ⱡ' => 'ⱡ',
'â±¢' => 'É«',
'â±£' => 'áµ½',
'Ɽ' => 'ɽ',
'Ⱨ' => 'ⱨ',
'Ⱪ' => 'ⱪ',
'Ⱬ' => 'ⱬ',
'â±­' => 'É‘',
'Ɱ' => 'ɱ',
'Ɐ' => 'É<>',
'â±°' => 'É’',
'â±²' => 'â±³',
'â±µ' => 'â±¶',
'â±¾' => 'È¿',
'Ɀ' => 'ɀ',
'â²€' => 'â²<C3A2>',
'Ⲃ' => 'ⲃ',
'Ⲅ' => 'ⲅ',
'Ⲇ' => 'ⲇ',
'Ⲉ' => 'ⲉ',
'Ⲋ' => 'ⲋ',
'Ⲍ' => 'â²<C3A2>',
'Ⲏ' => 'â²<C3A2>',
'â²<C3A2>' => 'ⲑ',
'Ⲓ' => 'ⲓ',
'Ⲕ' => 'ⲕ',
'â²–' => 'â²—',
'Ⲙ' => 'ⲙ',
'Ⲛ' => 'ⲛ',
'Ⲝ' => 'â²<C3A2>',
'Ⲟ' => 'ⲟ',
'Ⲡ' => 'ⲡ',
'â²¢' => 'â²£',
'Ⲥ' => 'ⲥ',
'Ⲧ' => 'ⲧ',
'Ⲩ' => 'ⲩ',
'Ⲫ' => 'ⲫ',
'Ⲭ' => 'ⲭ',
'Ⲯ' => 'ⲯ',
'â²°' => 'â²±',
'â²²' => 'â²³',
'â²´' => 'â²µ',
'â²¶' => 'â²·',
'Ⲹ' => 'ⲹ',
'Ⲻ' => 'ⲻ',
'â²¼' => 'â²½',
'Ⲿ' => 'ⲿ',
'â³€' => 'â³<C3A2>',
'Ⳃ' => 'ⳃ',
'Ⳅ' => 'ⳅ',
'Ⳇ' => 'ⳇ',
'Ⳉ' => 'ⳉ',
'Ⳋ' => 'ⳋ',
'Ⳍ' => 'â³<C3A2>',
'Ⳏ' => 'â³<C3A2>',
'â³<C3A2>' => 'ⳑ',
'Ⳓ' => 'ⳓ',
'Ⳕ' => 'ⳕ',
'â³–' => 'â³—',
'Ⳙ' => 'ⳙ',
'Ⳛ' => 'ⳛ',
'Ⳝ' => 'â³<C3A2>',
'Ⳟ' => 'ⳟ',
'Ⳡ' => 'ⳡ',
'â³¢' => 'â³£',
'Ⳬ' => 'ⳬ',
'â³­' => 'â³®',
'â³²' => 'â³³',
'Ꙁ' => 'ê™<C3AA>',
'Ꙃ' => 'ꙃ',
'Ꙅ' => 'ꙅ',
'Ꙇ' => 'ꙇ',
'Ꙉ' => 'ꙉ',
'Ꙋ' => 'ꙋ',
'Ꙍ' => 'ê™<C3AA>',
'Ꙏ' => 'ê™<C3AA>',
'ê™<C3AA>' => 'ꙑ',
'Ꙓ' => 'ꙓ',
'Ꙕ' => 'ꙕ',
'ê™–' => 'ê™—',
'Ꙙ' => 'ꙙ',
'Ꙛ' => 'ꙛ',
'Ꙝ' => 'ê™<C3AA>',
'Ꙟ' => 'ꙟ',
'Ꙡ' => 'ꙡ',
'Ꙣ' => 'ꙣ',
'Ꙥ' => 'ꙥ',
'Ꙧ' => 'ꙧ',
'Ꙩ' => 'ꙩ',
'Ꙫ' => 'ꙫ',
'Ꙭ' => 'ꙭ',
'Ꚁ' => 'êš<C3AA>',
'Ꚃ' => 'ꚃ',
'êš„' => 'êš…',
'Ꚇ' => 'ꚇ',
'Ꚉ' => 'ꚉ',
'Ꚋ' => 'ꚋ',
'Ꚍ' => 'êš<C3AA>',
'Ꚏ' => 'êš<C3AA>',
'êš<C3AA>' => 'êš‘',
'êš’' => 'êš“',
'êš”' => 'êš•',
'êš–' => 'êš—',
'Ꚙ' => 'ꚙ',
'êšš' => 'êš›',
'Ꜣ' => 'ꜣ',
'Ꜥ' => 'ꜥ',
'Ꜧ' => 'ꜧ',
'Ꜩ' => 'ꜩ',
'Ꜫ' => 'ꜫ',
'Ꜭ' => 'ꜭ',
'Ꜯ' => 'ꜯ',
'Ꜳ' => 'ꜳ',
'Ꜵ' => 'ꜵ',
'Ꜷ' => 'ꜷ',
'Ꜹ' => 'ꜹ',
'Ꜻ' => 'ꜻ',
'Ꜽ' => 'ꜽ',
'Ꜿ' => 'ꜿ',
<>€' => 'ê<><C3AA>',
<>' => 'ê<>ƒ',
<>„' => 'ê<>…',
<>†' => 'ê<>‡',
<>ˆ' => 'ê<>‰',
<>Š' => 'ê<>',
<>Œ' => 'ê<><C3AA>',
<>Ž' => 'ê<><C3AA>',
<><C3AA>' => 'ê<>',
<>' => 'ê<>“',
<>”' => 'ê<>•',
<>' => 'ê<>—',
<>˜' => 'ê<>™',
<>š' => 'ê<>',
<>œ' => 'ê<><C3AA>',
<>ž' => 'ê<>Ÿ',
<> ' => 'ê<>¡',
<>¢' => 'ê<>£',
<>¤' => 'ê<>¥',
<>¦' => 'ê<>§',
<>¨' => 'ê<>©',
<>ª' => 'ê<>«',
<>¬' => 'ê<>­',
<>®' => 'ê<>¯',
<>¹' => 'ê<>º',
<>»' => 'ê<>¼',
<>½' => 'áµ¹',
<>¾' => 'ê<>¿',
'Ꞁ' => 'êž<C3AA>',
'Ꞃ' => 'ꞃ',
'êž„' => 'êž…',
'Ꞇ' => 'ꞇ',
'Ꞌ' => 'ꞌ',
'êž<C3AA>' => 'É¥',
'êž<C3AA>' => 'êž‘',
'êž’' => 'êž“',
'êž–' => 'êž—',
'Ꞙ' => 'ꞙ',
'êžš' => 'êž›',
'êžœ' => 'êž<C3AA>',
'Ꞟ' => 'ꞟ',
'êž ' => 'êž¡',
'Ꞣ' => 'ꞣ',
'Ꞥ' => 'ꞥ',
'Ꞧ' => 'ꞧ',
'Ꞩ' => 'ꞩ',
'Ɦ' => 'ɦ',
'Ɜ' => 'ɜ',
'Ɡ' => 'ɡ',
'Ɬ' => 'ɬ',
'êž°' => 'Êž',
'Ʇ' => 'ʇ',
'A' => 'ï½<C3AF>',
'B' => 'b',
'C' => 'c',
'D' => 'd',
'ï¼¥' => 'ï½…',
'F' => 'f',
'G' => 'g',
'H' => 'h',
'I' => 'i',
'J' => 'j',
'K' => 'k',
'L' => 'l',
'ï¼­' => 'ï½<C3AF>',
'N' => 'n',
'O' => 'ï½<C3AF>',
'ï¼°' => 'ï½<C3AF>',
'Q' => 'q',
'ï¼²' => 'ï½’',
'S' => 's',
'ï¼´' => 'ï½”',
'U' => 'u',
'ï¼¶' => 'ï½–',
'ï¼·' => 'ï½—',
'X' => 'x',
'ï¼¹' => 'ï½™',
'Z' => 'z',
<><C3B0>€' => 'ð<><C3B0>¨',
<><C3B0><EFBFBD>' => 'ð<><C3B0>©',
<><C3B0>' => 'ð<><C3B0>ª',
<><C3B0>ƒ' => 'ð<><C3B0>«',
<><C3B0>„' => 'ð<><C3B0>¬',
<><C3B0>…' => 'ð<><C3B0>­',
<><C3B0>†' => 'ð<><C3B0>®',
<><C3B0>‡' => 'ð<><C3B0>¯',
<><C3B0>ˆ' => 'ð<><C3B0>°',
<><C3B0>‰' => 'ð<><C3B0>±',
<><C3B0>Š' => 'ð<><C3B0>²',
<><C3B0>' => 'ð<><C3B0>³',
<><C3B0>Œ' => 'ð<><C3B0>´',
<><C3B0><EFBFBD>' => 'ð<><C3B0>µ',
<><C3B0>Ž' => 'ð<><C3B0>¶',
<><C3B0><EFBFBD>' => 'ð<><C3B0>·',
<><C3B0><EFBFBD>' => 'ð<><C3B0>¸',
<><C3B0>' => 'ð<><C3B0>¹',
<><C3B0>' => 'ð<><C3B0>º',
<><C3B0>“' => 'ð<><C3B0>»',
<><C3B0>”' => 'ð<><C3B0>¼',
<><C3B0>•' => 'ð<><C3B0>½',
<><C3B0>' => 'ð<><C3B0>¾',
<><C3B0>—' => 'ð<><C3B0>¿',
<><C3B0>˜' => 'ð<>€',
<><C3B0>™' => 'ð<><EFBFBD>',
<><C3B0>š' => 'ð<>',
<><C3B0>' => 'ð<>ƒ',
<><C3B0>œ' => 'ð<>„',
<><C3B0><EFBFBD>' => 'ð<>…',
<><C3B0>ž' => 'ð<>†',
<><C3B0>Ÿ' => 'ð<>‡',
<><C3B0> ' => 'ð<>ˆ',
<><C3B0>¡' => 'ð<>‰',
<><C3B0>¢' => 'ð<>Š',
<><C3B0>£' => 'ð<>',
<><C3B0>¤' => 'ð<>Œ',
<><C3B0>¥' => 'ð<><EFBFBD>',
<><C3B0>¦' => 'ð<>Ž',
<><C3B0>§' => 'ð<><EFBFBD>',
'ð‘¢ ' => 'ð‘£€',
'𑢡' => 'ð£<E28098>',
'𑢢' => '𑣂',
'𑢣' => '𑣃',
'𑢤' => '𑣄',
'ð‘¢¥' => 'ð‘£…',
'𑢦' => '𑣆',
'𑢧' => '𑣇',
'𑢨' => '𑣈',
'𑢩' => '𑣉',
'𑢪' => '𑣊',
'𑢫' => '𑣋',
'𑢬' => '𑣌',
'ð‘¢­' => 'ð£<E28098>',
'𑢮' => '𑣎',
'𑢯' => 'ð£<E28098>',
'ð‘¢°' => 'ð£<E28098>',
'𑢱' => '𑣑',
'ð‘¢²' => 'ð‘£’',
'𑢳' => '𑣓',
'ð‘¢´' => 'ð‘£”',
'𑢵' => '𑣕',
'ð‘¢¶' => 'ð‘£–',
'ð‘¢·' => 'ð‘£—',
'𑢸' => '𑣘',
'ð‘¢¹' => 'ð‘£™',
'𑢺' => '𑣚',
'ð‘¢»' => 'ð‘£›',
'𑢼' => '𑣜',
'ð‘¢½' => 'ð£<E28098>',
'𑢾' => '𑣞',
'𑢿' => '𑣟',
);
<?php
// from Case_Ignorable in https://unicode.org/Public/UNIDATA/DerivedCoreProperties.txt
return '/(?<![\x{0027}\x{002E}\x{003A}\x{005E}\x{0060}\x{00A8}\x{00AD}\x{00AF}\x{00B4}\x{00B7}\x{00B8}\x{02B0}-\x{02C1}\x{02C2}-\x{02C5}\x{02C6}-\x{02D1}\x{02D2}-\x{02DF}\x{02E0}-\x{02E4}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EE}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037A}\x{0384}-\x{0385}\x{0387}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0559}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{05F4}\x{0600}-\x{0605}\x{0610}-\x{061A}\x{061C}\x{0640}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DD}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07FA}\x{07FD}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0971}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E46}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}\x{0EBB}-\x{0EBC}\x{0EC6}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{10FC}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17D7}\x{17DD}\x{180B}-\x{180D}\x{180E}\x{1843}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AA7}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1C78}-\x{1C7D}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1D2C}-\x{1D6A}\x{1D78}\x{1D9B}-\x{1DBF}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200F}\x{2018}\x{2019}\x{2024}\x{2027}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{206F}\x{2071}\x{207F}\x{2090}-\x{209C}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2C7C}-\x{2C7D}\x{2CEF}-\x{2CF1}\x{2D6F}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E2F}\x{3005}\x{302A}-\x{302D}\x{3031}-\x{3035}\x{303B}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{309D}-\x{309E}\x{30FC}-\x{30FE}\x{A015}\x{A4F8}-\x{A4FD}\x{A60C}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A67F}\x{A69C}-\x{A69D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A770}\x{A788}\x{A789}-\x{A78A}\x{A7F8}-\x{A7F9}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}\x{A9CF}\x{A9E5}\x{A9E6}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA70}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AADD}\x{AAEC}-\x{AAED}\x{AAF3}-\x{AAF4}\x{AAF6}\x{AB5B}\x{AB5C}-\x{AB5F}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FBB2}-\x{FBC1}\x{FE00}-\x{FE0F}\x{FE13}\x{FE20}-\x{FE2F}\x{FE52}\x{FE55}\x{FEFF}\x{FF07}\x{FF0E}\x{FF1A}\x{FF3E}\x{FF40}\x{FF70}\x{FF9E}-\x{FF9F}\x{FFE3}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110BD}\x{110CD}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16B40}-\x{16B43}\x{16F8F}-\x{16F92}\x{16F93}-\x{16F9F}\x{16FE0}-\x{16FE1}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1F3FB}-\x{1F3FF}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}])(\pL)(\pL*+)/u';
<?php
return array(
'a' => 'A',
'b' => 'B',
'c' => 'C',
'd' => 'D',
'e' => 'E',
'f' => 'F',
'g' => 'G',
'h' => 'H',
'i' => 'I',
'j' => 'J',
'k' => 'K',
'l' => 'L',
'm' => 'M',
'n' => 'N',
'o' => 'O',
'p' => 'P',
'q' => 'Q',
'r' => 'R',
's' => 'S',
't' => 'T',
'u' => 'U',
'v' => 'V',
'w' => 'W',
'x' => 'X',
'y' => 'Y',
'z' => 'Z',
'µ' => 'Μ',
'à' => 'À',
'á' => 'Ã<>',
'â' => 'Â',
'ã' => 'Ã',
'ä' => 'Ä',
'Ã¥' => 'Ã…',
'æ' => 'Æ',
'ç' => 'Ç',
'è' => 'È',
'é' => 'É',
'ê' => 'Ê',
'ë' => 'Ë',
'ì' => 'Ì',
'í' => 'Ã<>',
'î' => 'Î',
'ï' => 'Ã<>',
'ð' => 'Ã<>',
'ñ' => 'Ñ',
'ò' => 'Ò',
'ó' => 'Ó',
'ô' => 'Ô',
'õ' => 'Õ',
'ö' => 'Ö',
'ø' => 'Ø',
'ù' => 'Ù',
'ú' => 'Ú',
'û' => 'Û',
'ü' => 'Ü',
'ý' => 'Ã<>',
'þ' => 'Þ',
'ÿ' => 'Ÿ',
<>' => 'Ä€',
'ă' => 'Ă',
'Ä…' => 'Ä„',
'ć' => 'Ć',
'ĉ' => 'Ĉ',
'Ä‹' => 'ÄŠ',
<>' => 'Č',
<>' => 'ÄŽ',
'Ä‘' => 'Ä<>',
'Ä“' => 'Ä’',
'Ä•' => 'Ä”',
'Ä—' => 'Ä–',
'ę' => 'Ę',
'Ä›' => 'Äš',
<>' => 'Ĝ',
'ÄŸ' => 'Äž',
'Ä¡' => 'Ä ',
'Ä£' => 'Ä¢',
'ĥ' => 'Ĥ',
'ħ' => 'Ħ',
'ĩ' => 'Ĩ',
'ī' => 'Ī',
'ĭ' => 'Ĭ',
'į' => 'Į',
'ı' => 'I',
'ij' => 'IJ',
'ĵ' => 'Ĵ',
'ķ' => 'Ķ',
'ĺ' => 'Ĺ',
'ļ' => 'Ļ',
'ľ' => 'Ľ',
'Å€' => 'Ä¿',
'Å‚' => 'Å<>',
'ń' => 'Ń',
'ņ' => 'Ņ',
'ň' => 'Ň',
'Å‹' => 'ÅŠ',
<>' => 'Ō',
<>' => 'ÅŽ',
'Å‘' => 'Å<>',
'Å“' => 'Å’',
'Å•' => 'Å”',
'Å—' => 'Å–',
'ř' => 'Ř',
'Å›' => 'Åš',
<>' => 'Ŝ',
'ÅŸ' => 'Åž',
'Å¡' => 'Å ',
'Å£' => 'Å¢',
'ť' => 'Ť',
'ŧ' => 'Ŧ',
'ũ' => 'Ũ',
'ū' => 'Ū',
'ŭ' => 'Ŭ',
'ů' => 'Ů',
'ű' => 'Ű',
'ų' => 'Ų',
'ŵ' => 'Ŵ',
'ŷ' => 'Ŷ',
'ź' => 'Ź',
'ż' => 'Ż',
'ž' => 'Ž',
'Å¿' => 'S',
'ƀ' => 'Ƀ',
'ƃ' => 'Ƃ',
'Æ…' => 'Æ„',
'ƈ' => 'Ƈ',
'ƌ' => 'Ƌ',
'Æ’' => 'Æ‘',
'ƕ' => 'Ƕ',
'ƙ' => 'Ƙ',
'ƚ' => 'Ƚ',
'Æž' => 'È ',
'Æ¡' => 'Æ ',
'Æ£' => 'Æ¢',
'ƥ' => 'Ƥ',
'ƨ' => 'Ƨ',
'ƭ' => 'Ƭ',
'ư' => 'Ư',
'ƴ' => 'Ƴ',
'ƶ' => 'Ƶ',
'ƹ' => 'Ƹ',
'ƽ' => 'Ƽ',
'Æ¿' => 'Ç·',
'Ç…' => 'Ç„',
'dž' => 'DŽ',
'Lj' => 'LJ',
'lj' => 'LJ',
'Ç‹' => 'ÇŠ',
'nj' => 'NJ',
'ÇŽ' => 'Ç<>',
<>' => 'Ç<>',
'Ç’' => 'Ç‘',
'Ç”' => 'Ç“',
'Ç–' => 'Ç•',
'ǘ' => 'Ǘ',
'Çš' => 'Ç™',
'ǜ' => 'Ǜ',
<>' => 'ÆŽ',
'ÇŸ' => 'Çž',
'Ç¡' => 'Ç ',
'Ç£' => 'Ç¢',
'ǥ' => 'Ǥ',
'ǧ' => 'Ǧ',
'ǩ' => 'Ǩ',
'ǫ' => 'Ǫ',
'ǭ' => 'Ǭ',
'ǯ' => 'Ǯ',
'Dz' => 'DZ',
'dz' => 'DZ',
'ǵ' => 'Ǵ',
'ǹ' => 'Ǹ',
'ǻ' => 'Ǻ',
'ǽ' => 'Ǽ',
'ǿ' => 'Ǿ',
<>' => 'È€',
'ȃ' => 'Ȃ',
'È…' => 'È„',
'ȇ' => 'Ȇ',
'ȉ' => 'Ȉ',
'È‹' => 'ÈŠ',
<>' => 'Ȍ',
<>' => 'ÈŽ',
'È‘' => 'È<>',
'È“' => 'È’',
'È•' => 'È”',
'È—' => 'È–',
'ș' => 'Ș',
'È›' => 'Èš',
<>' => 'Ȝ',
'ÈŸ' => 'Èž',
'È£' => 'È¢',
'ȥ' => 'Ȥ',
'ȧ' => 'Ȧ',
'ȩ' => 'Ȩ',
'ȫ' => 'Ȫ',
'ȭ' => 'Ȭ',
'ȯ' => 'Ȯ',
'ȱ' => 'Ȱ',
'ȳ' => 'Ȳ',
'ȼ' => 'Ȼ',
'È¿' => 'â±¾',
'ɀ' => 'Ɀ',
'É‚' => 'É<>',
'ɇ' => 'Ɇ',
'ɉ' => 'Ɉ',
'É‹' => 'ÉŠ',
<>' => 'Ɍ',
<>' => 'ÉŽ',
<>' => 'Ɐ',
'É‘' => 'â±­',
'É’' => 'â±°',
'É“' => 'Æ<>',
'ɔ' => 'Ɔ',
'ɖ' => 'Ɖ',
'É—' => 'ÆŠ',
'É™' => 'Æ<>',
'É›' => 'Æ<>',
'ɜ' => 'Ɜ',
'É ' => 'Æ“',
'ɡ' => 'Ɡ',
'É£' => 'Æ”',
'É¥' => 'êž<C3AA>',
'ɦ' => 'Ɦ',
'ɨ' => 'Ɨ',
'É©' => 'Æ–',
'É«' => 'â±¢',
'ɬ' => 'Ɬ',
'ɯ' => 'Ɯ',
'ɱ' => 'Ɱ',
'ɲ' => 'Æ<>',
'ɵ' => 'Ɵ',
'ɽ' => 'Ɽ',
'ʀ' => 'Ʀ',
'ʃ' => 'Ʃ',
'ʇ' => 'Ʇ',
'ʈ' => 'Ʈ',
'ʉ' => 'Ʉ',
'ʊ' => 'Ʊ',
'ʋ' => 'Ʋ',
'ʌ' => 'Ʌ',
'Ê’' => 'Æ·',
'Êž' => 'êž°',
'ͅ' => 'Ι',
'ͱ' => 'Ͱ',
'ͳ' => 'Ͳ',
'ͷ' => 'Ͷ',
'ͻ' => 'Ͻ',
'ͼ' => 'Ͼ',
'ͽ' => 'Ͽ',
'ά' => 'Ά',
'έ' => 'Έ',
'ή' => 'Ή',
'ί' => 'Ί',
'α' => 'Α',
'β' => 'Β',
'γ' => 'Γ',
'δ' => 'Δ',
'ε' => 'Ε',
'ζ' => 'Ζ',
'η' => 'Η',
'θ' => 'Θ',
'ι' => 'Ι',
'κ' => 'Κ',
'λ' => 'Λ',
'μ' => 'Μ',
'ν' => 'Î<>',
'ξ' => 'Ξ',
'ο' => 'Ο',
'π' => 'Π',
<>' => 'Ρ',
'ς' => 'Σ',
'σ' => 'Σ',
'τ' => 'Τ',
'Ï…' => 'Î¥',
'φ' => 'Φ',
'χ' => 'Χ',
'ψ' => 'Ψ',
'ω' => 'Ω',
'ϊ' => 'Ϊ',
'ϋ' => 'Ϋ',
'ό' => 'Ό',
<>' => 'ÎŽ',
'ÏŽ' => 'Î<>',
<>' => 'Î’',
'ϑ' => 'Θ',
'ϕ' => 'Φ',
'ϖ' => 'Π',
'Ï—' => 'Ï<>',
'ϙ' => 'Ϙ',
'Ï›' => 'Ïš',
<>' => 'Ϝ',
'ÏŸ' => 'Ïž',
'Ï¡' => 'Ï ',
'Ï£' => 'Ï¢',
'ϥ' => 'Ϥ',
'ϧ' => 'Ϧ',
'ϩ' => 'Ϩ',
'ϫ' => 'Ϫ',
'ϭ' => 'Ϭ',
'ϯ' => 'Ϯ',
'ϰ' => 'Κ',
'ϱ' => 'Ρ',
'ϲ' => 'Ϲ',
'ϳ' => 'Ϳ',
'ϵ' => 'Ε',
'ϸ' => 'Ϸ',
'ϻ' => 'Ϻ',
'а' => 'Ð<>',
'б' => 'Б',
'в' => 'В',
'г' => 'Г',
'д' => 'Д',
'е' => 'Е',
'ж' => 'Ж',
'з' => 'З',
'и' => 'И',
'й' => 'Й',
'к' => 'К',
'л' => 'Л',
'м' => 'М',
'н' => 'Ð<>',
'о' => 'О',
'п' => 'П',
'р' => 'Р',
<>' => 'С',
'т' => 'Т',
'у' => 'У',
'ф' => 'Ф',
'Ñ…' => 'Ð¥',
'ц' => 'Ц',
'ч' => 'Ч',
'ш' => 'Ш',
'щ' => 'Щ',
'ъ' => 'Ъ',
'ы' => 'Ы',
'ь' => 'Ь',
<>' => 'Э',
'ю' => 'Ю',
<>' => 'Я',
<>' => 'Ѐ',
'Ñ‘' => 'Ð<>',
'ђ' => 'Ђ',
'ѓ' => 'Ѓ',
'є' => 'Є',
'Ñ•' => 'Ð…',
'і' => 'І',
'ї' => 'Ї',
'ј' => 'Ј',
'љ' => 'Љ',
'њ' => 'Њ',
'ћ' => 'Ћ',
'ќ' => 'Ќ',
<>' => 'Ð<>',
'Ñž' => 'ÐŽ',
'ÑŸ' => 'Ð<>',
'Ñ¡' => 'Ñ ',
'Ñ£' => 'Ñ¢',
'ѥ' => 'Ѥ',
'ѧ' => 'Ѧ',
'ѩ' => 'Ѩ',
'ѫ' => 'Ѫ',
'ѭ' => 'Ѭ',
'ѯ' => 'Ѯ',
'ѱ' => 'Ѱ',
'ѳ' => 'Ѳ',
'ѵ' => 'Ѵ',
'ѷ' => 'Ѷ',
'ѹ' => 'Ѹ',
'ѻ' => 'Ѻ',
'ѽ' => 'Ѽ',
'ѿ' => 'Ѿ',
<>' => 'Ò€',
'Ò‹' => 'ÒŠ',
<>' => 'Ҍ',
<>' => 'ÒŽ',
'Ò‘' => 'Ò<>',
'Ò“' => 'Ò’',
'Ò•' => 'Ò”',
'Ò—' => 'Ò–',
'Ò™' => 'Ò˜',
'Ò›' => 'Òš',
<>' => 'Ҝ',
'ÒŸ' => 'Òž',
'Ò¡' => 'Ò ',
'Ò£' => 'Ò¢',
'Ò¥' => 'Ò¤',
'Ò§' => 'Ò¦',
'Ò©' => 'Ò¨',
'Ò«' => 'Òª',
'Ò­' => 'Ò¬',
'Ò¯' => 'Ò®',
'Ò±' => 'Ò°',
'Ò³' => 'Ò²',
'Òµ' => 'Ò´',
'Ò·' => 'Ò¶',
'Ò¹' => 'Ò¸',
'Ò»' => 'Òº',
'Ò½' => 'Ò¼',
'Ò¿' => 'Ò¾',
'Ó‚' => 'Ó<>',
'Ó„' => 'Óƒ',
'Ó†' => 'Ó…',
'Óˆ' => 'Ó‡',
'ÓŠ' => 'Ó‰',
'ӌ' => 'Ӌ',
'ÓŽ' => 'Ó<>',
<>' => 'Ó€',
'Ó‘' => 'Ó<>',
'Ó“' => 'Ó’',
'Ó•' => 'Ó”',
'Ó—' => 'Ó–',
'Ó™' => 'Ó˜',
'Ó›' => 'Óš',
<>' => 'Ӝ',
'ÓŸ' => 'Óž',
'Ó¡' => 'Ó ',
'Ó£' => 'Ó¢',
'Ó¥' => 'Ó¤',
'Ó§' => 'Ó¦',
'Ó©' => 'Ó¨',
'Ó«' => 'Óª',
'Ó­' => 'Ó¬',
'Ó¯' => 'Ó®',
'Ó±' => 'Ó°',
'Ó³' => 'Ó²',
'Óµ' => 'Ó´',
'Ó·' => 'Ó¶',
'Ó¹' => 'Ó¸',
'Ó»' => 'Óº',
'Ó½' => 'Ó¼',
'Ó¿' => 'Ó¾',
<>' => 'Ô€',
'Ôƒ' => 'Ô‚',
'Ô…' => 'Ô„',
'Ô‡' => 'Ô†',
'Ô‰' => 'Ôˆ',
'Ô‹' => 'ÔŠ',
<>' => 'Ԍ',
<>' => 'ÔŽ',
'Ô‘' => 'Ô<>',
'Ô“' => 'Ô’',
'Ô•' => 'Ô”',
'Ô—' => 'Ô–',
'Ô™' => 'Ô˜',
'Ô›' => 'Ôš',
<>' => 'Ԝ',
'ÔŸ' => 'Ôž',
'Ô¡' => 'Ô ',
'Ô£' => 'Ô¢',
'Ô¥' => 'Ô¤',
'Ô§' => 'Ô¦',
'Ô©' => 'Ô¨',
'Ô«' => 'Ôª',
'Ô­' => 'Ô¬',
'Ô¯' => 'Ô®',
'Õ¡' => 'Ô±',
'Õ¢' => 'Ô²',
'Õ£' => 'Ô³',
'Õ¤' => 'Ô´',
'Õ¥' => 'Ôµ',
'Õ¦' => 'Ô¶',
'Õ§' => 'Ô·',
'Õ¨' => 'Ô¸',
'Õ©' => 'Ô¹',
'Õª' => 'Ôº',
'Õ«' => 'Ô»',
'Õ¬' => 'Ô¼',
'Õ­' => 'Ô½',
'Õ®' => 'Ô¾',
'Õ¯' => 'Ô¿',
'Õ°' => 'Õ€',
'Õ±' => 'Õ<>',
'Õ²' => 'Õ‚',
'Õ³' => 'Õƒ',
'Õ´' => 'Õ„',
'Õµ' => 'Õ…',
'Õ¶' => 'Õ†',
'Õ·' => 'Õ‡',
'Õ¸' => 'Õˆ',
'Õ¹' => 'Õ‰',
'Õº' => 'ÕŠ',
'Õ»' => 'Õ‹',
'ռ' => 'Ռ',
'Õ½' => 'Õ<>',
'Õ¾' => 'ÕŽ',
'Õ¿' => 'Õ<>',
'Ö€' => 'Õ<>',
<>' => 'Õ‘',
'Ö‚' => 'Õ’',
'Öƒ' => 'Õ“',
'Ö„' => 'Õ”',
'Ö…' => 'Õ•',
'Ö†' => 'Õ–',
'áµ¹' => 'ê<>½',
'áµ½' => 'â±£',
¸<C3A1>' => 'Ḁ',
'ḃ' => 'Ḃ',
'ḅ' => 'Ḅ',
'ḇ' => 'Ḇ',
'ḉ' => 'Ḉ',
'ḋ' => 'Ḋ',
¸<C3A1>' => 'Ḍ',
¸<C3A1>' => 'Ḏ',
'ḑ' => 'á¸<C3A1>',
'ḓ' => 'Ḓ',
'ḕ' => 'Ḕ',
'ḗ' => 'Ḗ',
'ḙ' => 'Ḙ',
'ḛ' => 'Ḛ',
¸<C3A1>' => 'Ḝ',
'ḟ' => 'Ḟ',
'ḡ' => 'Ḡ',
'ḣ' => 'Ḣ',
'ḥ' => 'Ḥ',
'ḧ' => 'Ḧ',
'ḩ' => 'Ḩ',
'ḫ' => 'Ḫ',
'ḭ' => 'Ḭ',
'ḯ' => 'Ḯ',
'ḱ' => 'Ḱ',
'ḳ' => 'Ḳ',
'ḵ' => 'Ḵ',
'ḷ' => 'Ḷ',
'ḹ' => 'Ḹ',
'ḻ' => 'Ḻ',
'ḽ' => 'Ḽ',
'ḿ' => 'Ḿ',
'á¹<C3A1>' => 'á¹€',
'ṃ' => 'Ṃ',
'ṅ' => 'Ṅ',
'ṇ' => 'Ṇ',
'ṉ' => 'Ṉ',
'ṋ' => 'Ṋ',
'á¹<C3A1>' => 'Ṍ',
'á¹<C3A1>' => 'Ṏ',
'ṑ' => 'á¹<C3A1>',
'ṓ' => 'Ṓ',
'ṕ' => 'Ṕ',
'á¹—' => 'á¹–',
'ṙ' => 'Ṙ',
'ṛ' => 'Ṛ',
'á¹<C3A1>' => 'Ṝ',
'ṟ' => 'Ṟ',
'ṡ' => 'Ṡ',
'á¹£' => 'á¹¢',
'ṥ' => 'Ṥ',
'ṧ' => 'Ṧ',
'ṩ' => 'Ṩ',
'ṫ' => 'Ṫ',
'ṭ' => 'Ṭ',
'ṯ' => 'Ṯ',
'á¹±' => 'á¹°',
'á¹³' => 'á¹²',
'á¹µ' => 'á¹´',
'á¹·' => 'á¹¶',
'ṹ' => 'Ṹ',
'ṻ' => 'Ṻ',
'á¹½' => 'á¹¼',
'ṿ' => 'Ṿ',
'áº<C3A1>' => 'Ẁ',
'ẃ' => 'Ẃ',
'ẅ' => 'Ẅ',
'ẇ' => 'Ẇ',
'ẉ' => 'Ẉ',
'ẋ' => 'Ẋ',
'áº<C3A1>' => 'Ẍ',
'áº<C3A1>' => 'Ẏ',
'ẑ' => 'áº<C3A1>',
'ẓ' => 'Ẓ',
'ẕ' => 'Ẕ',
'ẛ' => 'Ṡ',
'ạ' => 'Ạ',
'ả' => 'Ả',
'ấ' => 'Ấ',
'ầ' => 'Ầ',
'ẩ' => 'Ẩ',
'ẫ' => 'Ẫ',
'ậ' => 'Ậ',
'ắ' => 'Ắ',
'ằ' => 'Ằ',
'ẳ' => 'Ẳ',
'ẵ' => 'Ẵ',
'ặ' => 'Ặ',
'ẹ' => 'Ẹ',
'ẻ' => 'Ẻ',
'ẽ' => 'Ẽ',
'ế' => 'Ế',
'á»<C3A1>' => 'Ề',
'ể' => 'Ể',
'ễ' => 'Ễ',
'ệ' => 'Ệ',
'ỉ' => 'Ỉ',
'ị' => 'Ị',
'á»<C3A1>' => 'Ọ',
'á»<C3A1>' => 'Ỏ',
'ố' => 'á»<C3A1>',
'ồ' => 'Ồ',
'ổ' => 'Ổ',
'á»—' => 'á»–',
'ộ' => 'Ộ',
'ớ' => 'Ớ',
'á»<C3A1>' => 'Ờ',
'ở' => 'Ở',
'ỡ' => 'Ỡ',
'ợ' => 'Ợ',
'ụ' => 'Ụ',
'ủ' => 'Ủ',
'ứ' => 'Ứ',
'ừ' => 'Ừ',
'ử' => 'Ử',
'ữ' => 'Ữ',
'á»±' => 'á»°',
'ỳ' => 'Ỳ',
'ỵ' => 'Ỵ',
'á»·' => 'á»¶',
'ỹ' => 'Ỹ',
'ỻ' => 'Ỻ',
'ỽ' => 'Ỽ',
'ỿ' => 'Ỿ',
'ἀ' => 'Ἀ',
'á¼<C3A1>' => 'Ἁ',
'ἂ' => 'Ἂ',
'ἃ' => 'Ἃ',
'ἄ' => 'Ἄ',
'á¼…' => 'á¼<C3A1>',
'ἆ' => 'Ἆ',
'ἇ' => 'á¼<C3A1>',
'á¼<C3A1>' => 'Ἐ',
'ἑ' => 'Ἑ',
'ἒ' => 'Ἒ',
'ἓ' => 'Ἓ',
'ἔ' => 'Ἔ',
'ἕ' => 'á¼<C3A1>',
'ἠ' => 'Ἠ',
'ἡ' => 'Ἡ',
'ἢ' => 'Ἢ',
'ἣ' => 'Ἣ',
'ἤ' => 'Ἤ',
'á¼¥' => 'á¼­',
'ἦ' => 'Ἦ',
'ἧ' => 'Ἧ',
'ἰ' => 'Ἰ',
'á¼±' => 'á¼¹',
'ἲ' => 'Ἲ',
'á¼³' => 'á¼»',
'á¼´' => 'á¼¼',
'á¼µ' => 'á¼½',
'á¼¶' => 'á¼¾',
'ἷ' => 'Ἷ',
'ὀ' => 'Ὀ',
'á½<C3A1>' => 'Ὁ',
'ὂ' => 'Ὂ',
'ὃ' => 'Ὃ',
'ὄ' => 'Ὄ',
'á½…' => 'á½<C3A1>',
'ὑ' => 'Ὑ',
'ὓ' => 'Ὓ',
'ὕ' => 'á½<C3A1>',
'ὗ' => 'Ὗ',
'ὠ' => 'Ὠ',
'ὡ' => 'Ὡ',
'ὢ' => 'Ὢ',
'ὣ' => 'Ὣ',
'ὤ' => 'Ὤ',
'á½¥' => 'á½­',
'ὦ' => 'Ὦ',
'ὧ' => 'Ὧ',
'ὰ' => 'Ὰ',
'á½±' => 'á¾»',
'ὲ' => 'Ὲ',
'έ' => 'Έ',
'ὴ' => 'Ὴ',
'á½µ' => 'á¿‹',
'ὶ' => 'Ὶ',
'á½·' => 'á¿›',
'ὸ' => 'Ὸ',
'ό' => 'Ό',
'ὺ' => 'Ὺ',
'á½»' => 'á¿«',
'ὼ' => 'Ὼ',
'á½½' => 'á¿»',
'ᾀ' => 'ᾈ',
'á¾<C3A1>' => 'ᾉ',
'ᾂ' => 'ᾊ',
'ᾃ' => 'ᾋ',
'ᾄ' => 'ᾌ',
'á¾…' => 'á¾<C3A1>',
'ᾆ' => 'ᾎ',
'ᾇ' => 'á¾<C3A1>',
'á¾<C3A1>' => 'ᾘ',
'ᾑ' => 'ᾙ',
'ᾒ' => 'ᾚ',
'ᾓ' => 'ᾛ',
'ᾔ' => 'ᾜ',
'ᾕ' => 'á¾<C3A1>',
'ᾖ' => 'ᾞ',
'ᾗ' => 'ᾟ',
'ᾠ' => 'ᾨ',
'ᾡ' => 'ᾩ',
'ᾢ' => 'ᾪ',
'ᾣ' => 'ᾫ',
'ᾤ' => 'ᾬ',
'á¾¥' => 'á¾­',
'ᾦ' => 'ᾮ',
'ᾧ' => 'ᾯ',
'ᾰ' => 'Ᾰ',
'á¾±' => 'á¾¹',
'á¾³' => 'á¾¼',
'ι' => 'Ι',
'ῃ' => 'ῌ',
'á¿<C3A1>' => 'Ῐ',
'á¿‘' => 'á¿™',
'ῠ' => 'Ῠ',
'á¿¡' => 'á¿©',
'ῥ' => 'Ῥ',
'ῳ' => 'ῼ',
'ⅎ' => 'Ⅎ',
'â…°' => 'â… ',
'â…±' => 'â…¡',
'â…²' => 'â…¢',
'â…³' => 'â…£',
'â…´' => 'â…¤',
'â…µ' => 'â…¥',
'â…¶' => 'â…¦',
'â…·' => 'â…§',
'â…¸' => 'â…¨',
'â…¹' => 'â…©',
'â…º' => 'â…ª',
'â…»' => 'â…«',
'â…¼' => 'â…¬',
'â…½' => 'â…­',
'â…¾' => 'â…®',
'â…¿' => 'â…¯',
'ↄ' => 'Ↄ',
'â“<C3A2>' => 'â’¶',
'â“‘' => 'â’·',
'â“’' => 'â’¸',
'â““' => 'â’¹',
'â“”' => 'â’º',
'â“•' => 'â’»',
'â“–' => 'â’¼',
'â“—' => 'â’½',
'ⓘ' => 'Ⓘ',
'â“™' => 'â’¿',
'ⓚ' => 'Ⓚ',
'â“›' => 'â“<C3A2>',
'ⓜ' => 'Ⓜ',
'â“<C3A2>' => 'Ⓝ',
'ⓞ' => 'Ⓞ',
'ⓟ' => 'Ⓟ',
'ⓠ' => 'Ⓠ',
'ⓡ' => 'Ⓡ',
'ⓢ' => 'Ⓢ',
'ⓣ' => 'Ⓣ',
'ⓤ' => 'Ⓤ',
'â“¥' => 'â“‹',
'ⓦ' => 'Ⓦ',
'â“§' => 'â“<C3A2>',
'ⓨ' => 'Ⓨ',
'â“©' => 'â“<C3A2>',
'â°°' => 'â°€',
'â°±' => 'â°<C3A2>',
'â°²' => 'â°‚',
'â°³' => 'â°ƒ',
'â°´' => 'â°„',
'â°µ' => 'â°…',
'â°¶' => 'â°†',
'â°·' => 'â°‡',
'â°¸' => 'â°ˆ',
'â°¹' => 'â°‰',
'â°º' => 'â°Š',
'â°»' => 'â°‹',
'ⰼ' => 'Ⰼ',
'â°½' => 'â°<C3A2>',
'â°¾' => 'â°Ž',
'â°¿' => 'â°<C3A2>',
'â±€' => 'â°<C3A2>',
'â±<C3A2>' => 'â°‘',
'ⱂ' => 'Ⱂ',
'ⱃ' => 'Ⱃ',
'ⱄ' => 'Ⱄ',
'â±…' => 'â°•',
'ⱆ' => 'Ⱆ',
'ⱇ' => 'Ⱇ',
'ⱈ' => 'Ⱈ',
'ⱉ' => 'Ⱉ',
'ⱊ' => 'Ⱊ',
'ⱋ' => 'Ⱋ',
'ⱌ' => 'Ⱌ',
'â±<C3A2>' => 'â°<C3A2>',
'ⱎ' => 'Ⱎ',
'â±<C3A2>' => 'â°Ÿ',
'â±<C3A2>' => 'â° ',
'ⱑ' => 'Ⱑ',
'â±’' => 'â°¢',
'ⱓ' => 'Ⱓ',
'â±”' => 'â°¤',
'ⱕ' => 'Ⱕ',
'â±–' => 'â°¦',
'â±—' => 'â°§',
'ⱘ' => 'Ⱘ',
'â±™' => 'â°©',
'ⱚ' => 'Ⱚ',
'â±›' => 'â°«',
'ⱜ' => 'Ⱜ',
'â±<C3A2>' => 'â°­',
'ⱞ' => 'Ⱞ',
'ⱡ' => 'Ⱡ',
'ⱥ' => 'Ⱥ',
'ⱦ' => 'Ⱦ',
'ⱨ' => 'Ⱨ',
'ⱪ' => 'Ⱪ',
'ⱬ' => 'Ⱬ',
'â±³' => 'â±²',
'â±¶' => 'â±µ',
'â²<C3A2>' => 'â²€',
'ⲃ' => 'Ⲃ',
'ⲅ' => 'Ⲅ',
'ⲇ' => 'Ⲇ',
'ⲉ' => 'Ⲉ',
'ⲋ' => 'Ⲋ',
'â²<C3A2>' => 'Ⲍ',
'â²<C3A2>' => 'Ⲏ',
'ⲑ' => 'â²<C3A2>',
'ⲓ' => 'Ⲓ',
'ⲕ' => 'Ⲕ',
'â²—' => 'â²–',
'ⲙ' => 'Ⲙ',
'ⲛ' => 'Ⲛ',
'â²<C3A2>' => 'Ⲝ',
'ⲟ' => 'Ⲟ',
'ⲡ' => 'Ⲡ',
'â²£' => 'â²¢',
'ⲥ' => 'Ⲥ',
'ⲧ' => 'Ⲧ',
'ⲩ' => 'Ⲩ',
'ⲫ' => 'Ⲫ',
'ⲭ' => 'Ⲭ',
'ⲯ' => 'Ⲯ',
'â²±' => 'â²°',
'â²³' => 'â²²',
'â²µ' => 'â²´',
'â²·' => 'â²¶',
'ⲹ' => 'Ⲹ',
'ⲻ' => 'Ⲻ',
'â²½' => 'â²¼',
'ⲿ' => 'Ⲿ',
'â³<C3A2>' => 'â³€',
'ⳃ' => 'Ⳃ',
'ⳅ' => 'Ⳅ',
'ⳇ' => 'Ⳇ',
'ⳉ' => 'Ⳉ',
'ⳋ' => 'Ⳋ',
'â³<C3A2>' => 'Ⳍ',
'â³<C3A2>' => 'Ⳏ',
'ⳑ' => 'â³<C3A2>',
'ⳓ' => 'Ⳓ',
'ⳕ' => 'Ⳕ',
'â³—' => 'â³–',
'ⳙ' => 'Ⳙ',
'ⳛ' => 'Ⳛ',
'â³<C3A2>' => 'Ⳝ',
'ⳟ' => 'Ⳟ',
'ⳡ' => 'Ⳡ',
'â³£' => 'â³¢',
'ⳬ' => 'Ⳬ',
'â³®' => 'â³­',
'â³³' => 'â³²',
'â´€' => 'á‚ ',
´<C3A2>' => 'á‚¡',
'â´‚' => 'á‚¢',
'â´ƒ' => 'á‚£',
'ⴄ' => 'Ⴄ',
'â´…' => 'á‚¥',
'ⴆ' => 'Ⴆ',
'â´‡' => 'á‚§',
'ⴈ' => 'Ⴈ',
'â´‰' => 'á‚©',
'ⴊ' => 'Ⴊ',
'â´‹' => 'á‚«',
'ⴌ' => 'Ⴌ',
´<C3A2>' => 'á‚­',
'â´Ž' => 'á‚®',
´<C3A2>' => 'Ⴏ',
´<C3A2>' => 'á‚°',
'ⴑ' => 'Ⴑ',
'ⴒ' => 'Ⴒ',
'ⴓ' => 'Ⴓ',
'â´”' => 'á‚´',
'ⴕ' => 'Ⴕ',
'â´–' => 'á‚¶',
'â´—' => 'á‚·',
'ⴘ' => 'Ⴘ',
'ⴙ' => 'Ⴙ',
'ⴚ' => 'Ⴚ',
'â´›' => 'á‚»',
'ⴜ' => 'Ⴜ',
´<C3A2>' => 'Ⴝ',
'ⴞ' => 'Ⴞ',
'â´Ÿ' => 'á‚¿',
'ⴠ' => 'Ⴠ',
'â´¡' => 'áƒ<C3A1>',
'ⴢ' => 'Ⴢ',
'ⴣ' => 'Ⴣ',
'ⴤ' => 'Ⴤ',
'ⴥ' => 'Ⴥ',
'ⴧ' => 'Ⴧ',
'â´­' => 'áƒ<C3A1>',
'ê™<C3AA>' => 'Ꙁ',
'ꙃ' => 'Ꙃ',
'ꙅ' => 'Ꙅ',
'ꙇ' => 'Ꙇ',
'ꙉ' => 'Ꙉ',
'ꙋ' => 'Ꙋ',
'ê™<C3AA>' => 'Ꙍ',
'ê™<C3AA>' => 'Ꙏ',
'ꙑ' => 'ê™<C3AA>',
'ꙓ' => 'Ꙓ',
'ꙕ' => 'Ꙕ',
'ê™—' => 'ê™–',
'ꙙ' => 'Ꙙ',
'ꙛ' => 'Ꙛ',
'ê™<C3AA>' => 'Ꙝ',
'ꙟ' => 'Ꙟ',
'ꙡ' => 'Ꙡ',
'ꙣ' => 'Ꙣ',
'ꙥ' => 'Ꙥ',
'ꙧ' => 'Ꙧ',
'ꙩ' => 'Ꙩ',
'ꙫ' => 'Ꙫ',
'ꙭ' => 'Ꙭ',
'êš<C3AA>' => 'Ꚁ',
'ꚃ' => 'Ꚃ',
'êš…' => 'êš„',
'ꚇ' => 'Ꚇ',
'ꚉ' => 'Ꚉ',
'ꚋ' => 'Ꚋ',
'êš<C3AA>' => 'Ꚍ',
'êš<C3AA>' => 'Ꚏ',
'êš‘' => 'êš<C3AA>',
'êš“' => 'êš’',
'êš•' => 'êš”',
'êš—' => 'êš–',
'ꚙ' => 'Ꚙ',
'êš›' => 'êšš',
'ꜣ' => 'Ꜣ',
'ꜥ' => 'Ꜥ',
'ꜧ' => 'Ꜧ',
'ꜩ' => 'Ꜩ',
'ꜫ' => 'Ꜫ',
'ꜭ' => 'Ꜭ',
'ꜯ' => 'Ꜯ',
'ꜳ' => 'Ꜳ',
'ꜵ' => 'Ꜵ',
'ꜷ' => 'Ꜷ',
'ꜹ' => 'Ꜹ',
'ꜻ' => 'Ꜻ',
'ꜽ' => 'Ꜽ',
'ꜿ' => 'Ꜿ',
<><C3AA>' => 'ê<>€',
<>ƒ' => 'ê<>',
<>…' => 'ê<>„',
<>‡' => 'ê<>†',
<>‰' => 'ê<>ˆ',
<>' => 'ê<>Š',
<><C3AA>' => 'ê<>Œ',
<><C3AA>' => 'ê<>Ž',
<>' => 'ê<><C3AA>',
<>“' => 'ê<>',
<>•' => 'ê<>”',
<>—' => 'ê<>',
<>™' => 'ê<>˜',
<>' => 'ê<>š',
<><C3AA>' => 'ê<>œ',
<>Ÿ' => 'ê<>ž',
<>¡' => 'ê<> ',
<>£' => 'ê<>¢',
<>¥' => 'ê<>¤',
<>§' => 'ê<>¦',
<>©' => 'ê<>¨',
<>«' => 'ê<>ª',
<>­' => 'ê<>¬',
<>¯' => 'ê<>®',
<>º' => 'ê<>¹',
<>¼' => 'ê<>»',
<>¿' => 'ê<>¾',
'êž<C3AA>' => 'Ꞁ',
'ꞃ' => 'Ꞃ',
'êž…' => 'êž„',
'ꞇ' => 'Ꞇ',
'ꞌ' => 'Ꞌ',
'êž‘' => 'êž<C3AA>',
'êž“' => 'êž’',
'êž—' => 'êž–',
'ꞙ' => 'Ꞙ',
'êž›' => 'êžš',
'êž<C3AA>' => 'êžœ',
'ꞟ' => 'Ꞟ',
'êž¡' => 'êž ',
'ꞣ' => 'Ꞣ',
'ꞥ' => 'Ꞥ',
'ꞧ' => 'Ꞧ',
'ꞩ' => 'Ꞩ',
'ï½<C3AF>' => 'A',
'b' => 'B',
'c' => 'C',
'd' => 'D',
'ï½…' => 'ï¼¥',
'f' => 'F',
'g' => 'G',
'h' => 'H',
'i' => 'I',
'j' => 'J',
'k' => 'K',
'l' => 'L',
'ï½<C3AF>' => 'ï¼­',
'n' => 'N',
'ï½<C3AF>' => 'O',
'ï½<C3AF>' => 'ï¼°',
'q' => 'Q',
'ï½’' => 'ï¼²',
's' => 'S',
'ï½”' => 'ï¼´',
'u' => 'U',
'ï½–' => 'ï¼¶',
'ï½—' => 'ï¼·',
'x' => 'X',
'ï½™' => 'ï¼¹',
'z' => 'Z',
<><C3B0>¨' => 'ð<><C3B0>€',
<><C3B0>©' => 'ð<><C3B0><EFBFBD>',
<><C3B0>ª' => 'ð<><C3B0>',
<><C3B0>«' => 'ð<><C3B0>ƒ',
<><C3B0>¬' => 'ð<><C3B0>„',
<><C3B0>­' => 'ð<><C3B0>…',
<><C3B0>®' => 'ð<><C3B0>†',
<><C3B0>¯' => 'ð<><C3B0>‡',
<><C3B0>°' => 'ð<><C3B0>ˆ',
<><C3B0>±' => 'ð<><C3B0>‰',
<><C3B0>²' => 'ð<><C3B0>Š',
<><C3B0>³' => 'ð<><C3B0>',
<><C3B0>´' => 'ð<><C3B0>Œ',
<><C3B0>µ' => 'ð<><C3B0><EFBFBD>',
<><C3B0>¶' => 'ð<><C3B0>Ž',
<><C3B0>·' => 'ð<><C3B0><EFBFBD>',
<><C3B0>¸' => 'ð<><C3B0><EFBFBD>',
<><C3B0>¹' => 'ð<><C3B0>',
<><C3B0>º' => 'ð<><C3B0>',
<><C3B0>»' => 'ð<><C3B0>“',
<><C3B0>¼' => 'ð<><C3B0>”',
<><C3B0>½' => 'ð<><C3B0>•',
<><C3B0>¾' => 'ð<><C3B0>',
<><C3B0>¿' => 'ð<><C3B0>—',
<>€' => 'ð<><C3B0>˜',
<><EFBFBD>' => 'ð<><C3B0>™',
<>' => 'ð<><C3B0>š',
<>ƒ' => 'ð<><C3B0>',
<>„' => 'ð<><C3B0>œ',
<>…' => 'ð<><C3B0><EFBFBD>',
<>†' => 'ð<><C3B0>ž',
<>‡' => 'ð<><C3B0>Ÿ',
<>ˆ' => 'ð<><C3B0> ',
<>‰' => 'ð<><C3B0>¡',
<>Š' => 'ð<><C3B0>¢',
<>' => 'ð<><C3B0>£',
<>Œ' => 'ð<><C3B0>¤',
<><EFBFBD>' => 'ð<><C3B0>¥',
<>Ž' => 'ð<><C3B0>¦',
<><EFBFBD>' => 'ð<><C3B0>§',
'ð‘£€' => 'ð‘¢ ',
£<E28098>' => '𑢡',
'𑣂' => '𑢢',
'𑣃' => '𑢣',
'𑣄' => '𑢤',
'ð‘£…' => 'ð‘¢¥',
'𑣆' => '𑢦',
'𑣇' => '𑢧',
'𑣈' => '𑢨',
'𑣉' => '𑢩',
'𑣊' => '𑢪',
'𑣋' => '𑢫',
'𑣌' => '𑢬',
£<E28098>' => 'ð‘¢­',
'𑣎' => '𑢮',
£<E28098>' => '𑢯',
£<E28098>' => 'ð‘¢°',
'𑣑' => '𑢱',
'ð‘£’' => 'ð‘¢²',
'𑣓' => '𑢳',
'ð‘£”' => 'ð‘¢´',
'𑣕' => '𑢵',
'ð‘£–' => 'ð‘¢¶',
'ð‘£—' => 'ð‘¢·',
'𑣘' => '𑢸',
'ð‘£™' => 'ð‘¢¹',
'𑣚' => '𑢺',
'ð‘£›' => 'ð‘¢»',
'𑣜' => '𑢼',
£<E28098>' => 'ð‘¢½',
'𑣞' => '𑢾',
'𑣟' => '𑢿',
);
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Mbstring;
/**
* Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
*
* Implemented:
* - mb_chr - Returns a specific character from its Unicode code point
* - mb_convert_encoding - Convert character encoding
* - mb_convert_variables - Convert character code in variable(s)
* - mb_decode_mimeheader - Decode string in MIME header field
* - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
* - mb_decode_numericentity - Decode HTML numeric string reference to character
* - mb_encode_numericentity - Encode character to HTML numeric string reference
* - mb_convert_case - Perform case folding on a string
* - mb_detect_encoding - Detect character encoding
* - mb_get_info - Get internal settings of mbstring
* - mb_http_input - Detect HTTP input character encoding
* - mb_http_output - Set/Get HTTP output character encoding
* - mb_internal_encoding - Set/Get internal character encoding
* - mb_list_encodings - Returns an array of all supported encodings
* - mb_ord - Returns the Unicode code point of a character
* - mb_output_handler - Callback function converts character encoding in output buffer
* - mb_scrub - Replaces ill-formed byte sequences with substitute characters
* - mb_strlen - Get string length
* - mb_strpos - Find position of first occurrence of string in a string
* - mb_strrpos - Find position of last occurrence of a string in a string
* - mb_str_split - Convert a string to an array
* - mb_strtolower - Make a string lowercase
* - mb_strtoupper - Make a string uppercase
* - mb_substitute_character - Set/Get substitution character
* - mb_substr - Get part of string
* - mb_stripos - Finds position of first occurrence of a string within another, case insensitive
* - mb_stristr - Finds first occurrence of a string within another, case insensitive
* - mb_strrchr - Finds the last occurrence of a character in a string within another
* - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive
* - mb_strripos - Finds position of last occurrence of a string within another, case insensitive
* - mb_strstr - Finds first occurrence of a string within another
* - mb_strwidth - Return width of string
* - mb_substr_count - Count the number of substring occurrences
*
* Not implemented:
* - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
* - mb_ereg_* - Regular expression with multibyte support
* - mb_parse_str - Parse GET/POST/COOKIE data and set global variable
* - mb_preferred_mime_name - Get MIME charset string
* - mb_regex_encoding - Returns current encoding for multibyte regex as string
* - mb_regex_set_options - Set/Get the default options for mbregex functions
* - mb_send_mail - Send encoded mail
* - mb_split - Split multibyte string using regular expression
* - mb_strcut - Get part of string
* - mb_strimwidth - Get truncated string with specified width
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class Mbstring
{
const MB_CASE_FOLD = PHP_INT_MAX;
private static $encodingList = array('ASCII', 'UTF-8');
private static $language = 'neutral';
private static $internalEncoding = 'UTF-8';
private static $caseFold = array(
array('µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"),
array('μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'Ï€', 'κ', 'Ï<>', 'ε', "\xE1\xB9\xA1", 'ι'),
);
public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
{
if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) {
$fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
} else {
$fromEncoding = self::getEncoding($fromEncoding);
}
$toEncoding = self::getEncoding($toEncoding);
if ('BASE64' === $fromEncoding) {
$s = base64_decode($s);
$fromEncoding = $toEncoding;
}
if ('BASE64' === $toEncoding) {
return base64_encode($s);
}
if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
$fromEncoding = 'Windows-1252';
}
if ('UTF-8' !== $fromEncoding) {
$s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
}
return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s);
}
if ('HTML-ENTITIES' === $fromEncoding) {
$s = html_entity_decode($s, ENT_COMPAT, 'UTF-8');
$fromEncoding = 'UTF-8';
}
return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
}
public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null)
{
$vars = array(&$a, &$b, &$c, &$d, &$e, &$f);
$ok = true;
array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
$ok = false;
}
});
return $ok ? $fromEncoding : false;
}
public static function mb_decode_mimeheader($s)
{
return iconv_mime_decode($s, 2, self::$internalEncoding);
}
public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
{
trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING);
}
public static function mb_decode_numericentity($s, $convmap, $encoding = null)
{
if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) {
trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING);
return null;
}
if (!\is_array($convmap) || !$convmap) {
return false;
}
if (null !== $encoding && !\is_scalar($encoding)) {
trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING);
return ''; // Instead of null (cf. mb_encode_numericentity).
}
$s = (string) $s;
if ('' === $s) {
return '';
}
$encoding = self::getEncoding($encoding);
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
}
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
$cnt = floor(\count($convmap) / 4) * 4;
for ($i = 0; $i < $cnt; $i += 4) {
// collector_decode_htmlnumericentity ignores $convmap[$i + 3]
$convmap[$i] += $convmap[$i + 2];
$convmap[$i + 1] += $convmap[$i + 2];
}
$s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
$c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
for ($i = 0; $i < $cnt; $i += 4) {
if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
return Mbstring::mb_chr($c - $convmap[$i + 2]);
}
}
return $m[0];
}, $s);
if (null === $encoding) {
return $s;
}
return iconv('UTF-8', $encoding.'//IGNORE', $s);
}
public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
{
if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) {
trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING);
return null;
}
if (!\is_array($convmap) || !$convmap) {
return false;
}
if (null !== $encoding && !\is_scalar($encoding)) {
trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING);
return null; // Instead of '' (cf. mb_decode_numericentity).
}
if (null !== $is_hex && !\is_scalar($is_hex)) {
trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', E_USER_WARNING);
return null;
}
$s = (string) $s;
if ('' === $s) {
return '';
}
$encoding = self::getEncoding($encoding);
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
}
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
$cnt = floor(\count($convmap) / 4) * 4;
$i = 0;
$len = \strlen($s);
$result = '';
while ($i < $len) {
$ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
$uchr = substr($s, $i, $ulen);
$i += $ulen;
$c = self::mb_ord($uchr);
for ($j = 0; $j < $cnt; $j += 4) {
if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
$cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
$result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
continue 2;
}
}
$result .= $uchr;
}
if (null === $encoding) {
return $result;
}
return iconv('UTF-8', $encoding.'//IGNORE', $result);
}
public static function mb_convert_case($s, $mode, $encoding = null)
{
$s = (string) $s;
if ('' === $s) {
return '';
}
$encoding = self::getEncoding($encoding);
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
}
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
if (MB_CASE_TITLE == $mode) {
static $titleRegexp = null;
if (null === $titleRegexp) {
$titleRegexp = self::getData('titleCaseRegexp');
}
$s = preg_replace_callback($titleRegexp, array(__CLASS__, 'title_case'), $s);
} else {
if (MB_CASE_UPPER == $mode) {
static $upper = null;
if (null === $upper) {
$upper = self::getData('upperCase');
}
$map = $upper;
} else {
if (self::MB_CASE_FOLD === $mode) {
$s = str_replace(self::$caseFold[0], self::$caseFold[1], $s);
}
static $lower = null;
if (null === $lower) {
$lower = self::getData('lowerCase');
}
$map = $lower;
}
static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
$i = 0;
$len = \strlen($s);
while ($i < $len) {
$ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
$uchr = substr($s, $i, $ulen);
$i += $ulen;
if (isset($map[$uchr])) {
$uchr = $map[$uchr];
$nlen = \strlen($uchr);
if ($nlen == $ulen) {
$nlen = $i;
do {
$s[--$nlen] = $uchr[--$ulen];
} while ($ulen);
} else {
$s = substr_replace($s, $uchr, $i - $ulen, $ulen);
$len += $nlen - $ulen;
$i += $nlen - $ulen;
}
}
}
}
if (null === $encoding) {
return $s;
}
return iconv('UTF-8', $encoding.'//IGNORE', $s);
}
public static function mb_internal_encoding($encoding = null)
{
if (null === $encoding) {
return self::$internalEncoding;
}
$encoding = self::getEncoding($encoding);
if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) {
self::$internalEncoding = $encoding;
return true;
}
return false;
}
public static function mb_language($lang = null)
{
if (null === $lang) {
return self::$language;
}
switch ($lang = strtolower($lang)) {
case 'uni':
case 'neutral':
self::$language = $lang;
return true;
}
return false;
}
public static function mb_list_encodings()
{
return array('UTF-8');
}
public static function mb_encoding_aliases($encoding)
{
switch (strtoupper($encoding)) {
case 'UTF8':
case 'UTF-8':
return array('utf8');
}
return false;
}
public static function mb_check_encoding($var = null, $encoding = null)
{
if (null === $encoding) {
if (null === $var) {
return false;
}
$encoding = self::$internalEncoding;
}
return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var);
}
public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
{
if (null === $encodingList) {
$encodingList = self::$encodingList;
} else {
if (!\is_array($encodingList)) {
$encodingList = array_map('trim', explode(',', $encodingList));
}
$encodingList = array_map('strtoupper', $encodingList);
}
foreach ($encodingList as $enc) {
switch ($enc) {
case 'ASCII':
if (!preg_match('/[\x80-\xFF]/', $str)) {
return $enc;
}
break;
case 'UTF8':
case 'UTF-8':
if (preg_match('//u', $str)) {
return 'UTF-8';
}
break;
default:
if (0 === strncmp($enc, 'ISO-8859-', 9)) {
return $enc;
}
}
}
return false;
}
public static function mb_detect_order($encodingList = null)
{
if (null === $encodingList) {
return self::$encodingList;
}
if (!\is_array($encodingList)) {
$encodingList = array_map('trim', explode(',', $encodingList));
}
$encodingList = array_map('strtoupper', $encodingList);
foreach ($encodingList as $enc) {
switch ($enc) {
default:
if (strncmp($enc, 'ISO-8859-', 9)) {
return false;
}
// no break
case 'ASCII':
case 'UTF8':
case 'UTF-8':
}
}
self::$encodingList = $encodingList;
return true;
}
public static function mb_strlen($s, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return \strlen($s);
}
return @iconv_strlen($s, $encoding);
}
public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return strpos($haystack, $needle, $offset);
}
$needle = (string) $needle;
if ('' === $needle) {
trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING);
return false;
}
return iconv_strpos($haystack, $needle, $offset, $encoding);
}
public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return strrpos($haystack, $needle, $offset);
}
if ($offset != (int) $offset) {
$offset = 0;
} elseif ($offset = (int) $offset) {
if ($offset < 0) {
if (0 > $offset += self::mb_strlen($needle)) {
$haystack = self::mb_substr($haystack, 0, $offset, $encoding);
}
$offset = 0;
} else {
$haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
}
}
$pos = iconv_strrpos($haystack, $needle, $encoding);
return false !== $pos ? $offset + $pos : false;
}
public static function mb_str_split($string, $split_length = 1, $encoding = null)
{
if (null !== $string && !\is_scalar($string) && !(\is_object($string) && \method_exists($string, '__toString'))) {
trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', E_USER_WARNING);
return null;
}
if (1 > $split_length = (int) $split_length) {
trigger_error('The length of each segment must be greater than zero', E_USER_WARNING);
return false;
}
if (null === $encoding) {
$encoding = mb_internal_encoding();
}
if ('UTF-8' === $encoding = self::getEncoding($encoding)) {
$rx = '/(';
while (65535 < $split_length) {
$rx .= '.{65535}';
$split_length -= 65535;
}
$rx .= '.{'.$split_length.'})/us';
return preg_split($rx, $string, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
}
$result = array();
$length = mb_strlen($string, $encoding);
for ($i = 0; $i < $length; $i += $split_length) {
$result[] = mb_substr($string, $i, $split_length, $encoding);
}
return $result;
}
public static function mb_strtolower($s, $encoding = null)
{
return self::mb_convert_case($s, MB_CASE_LOWER, $encoding);
}
public static function mb_strtoupper($s, $encoding = null)
{
return self::mb_convert_case($s, MB_CASE_UPPER, $encoding);
}
public static function mb_substitute_character($c = null)
{
if (0 === strcasecmp($c, 'none')) {
return true;
}
return null !== $c ? false : 'none';
}
public static function mb_substr($s, $start, $length = null, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return (string) substr($s, $start, null === $length ? 2147483647 : $length);
}
if ($start < 0) {
$start = iconv_strlen($s, $encoding) + $start;
if ($start < 0) {
$start = 0;
}
}
if (null === $length) {
$length = 2147483647;
} elseif ($length < 0) {
$length = iconv_strlen($s, $encoding) + $length - $start;
if ($length < 0) {
return '';
}
}
return (string) iconv_substr($s, $start, $length, $encoding);
}
public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
{
$haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
$needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
return self::mb_strpos($haystack, $needle, $offset, $encoding);
}
public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
{
$pos = self::mb_stripos($haystack, $needle, 0, $encoding);
return self::getSubpart($pos, $part, $haystack, $encoding);
}
public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return strrchr($haystack, $needle, $part);
}
$needle = self::mb_substr($needle, 0, 1, $encoding);
$pos = iconv_strrpos($haystack, $needle, $encoding);
return self::getSubpart($pos, $part, $haystack, $encoding);
}
public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
{
$needle = self::mb_substr($needle, 0, 1, $encoding);
$pos = self::mb_strripos($haystack, $needle, $encoding);
return self::getSubpart($pos, $part, $haystack, $encoding);
}
public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
{
$haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
$needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
return self::mb_strrpos($haystack, $needle, $offset, $encoding);
}
public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
{
$pos = strpos($haystack, $needle);
if (false === $pos) {
return false;
}
if ($part) {
return substr($haystack, 0, $pos);
}
return substr($haystack, $pos);
}
public static function mb_get_info($type = 'all')
{
$info = array(
'internal_encoding' => self::$internalEncoding,
'http_output' => 'pass',
'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
'func_overload' => 0,
'func_overload_list' => 'no overload',
'mail_charset' => 'UTF-8',
'mail_header_encoding' => 'BASE64',
'mail_body_encoding' => 'BASE64',
'illegal_chars' => 0,
'encoding_translation' => 'Off',
'language' => self::$language,
'detect_order' => self::$encodingList,
'substitute_character' => 'none',
'strict_detection' => 'Off',
);
if ('all' === $type) {
return $info;
}
if (isset($info[$type])) {
return $info[$type];
}
return false;
}
public static function mb_http_input($type = '')
{
return false;
}
public static function mb_http_output($encoding = null)
{
return null !== $encoding ? 'pass' === $encoding : 'pass';
}
public static function mb_strwidth($s, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('UTF-8' !== $encoding) {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
$s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);
return ($wide << 1) + iconv_strlen($s, 'UTF-8');
}
public static function mb_substr_count($haystack, $needle, $encoding = null)
{
return substr_count($haystack, $needle);
}
public static function mb_output_handler($contents, $status)
{
return $contents;
}
public static function mb_chr($code, $encoding = null)
{
if (0x80 > $code %= 0x200000) {
$s = \chr($code);
} elseif (0x800 > $code) {
$s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
} elseif (0x10000 > $code) {
$s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
} else {
$s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
}
if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
$s = mb_convert_encoding($s, $encoding, 'UTF-8');
}
return $s;
}
public static function mb_ord($s, $encoding = null)
{
if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
$s = mb_convert_encoding($s, 'UTF-8', $encoding);
}
if (1 === \strlen($s)) {
return \ord($s);
}
$code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
if (0xF0 <= $code) {
return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
}
if (0xE0 <= $code) {
return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
}
if (0xC0 <= $code) {
return (($code - 0xC0) << 6) + $s[2] - 0x80;
}
return $code;
}
private static function getSubpart($pos, $part, $haystack, $encoding)
{
if (false === $pos) {
return false;
}
if ($part) {
return self::mb_substr($haystack, 0, $pos, $encoding);
}
return self::mb_substr($haystack, $pos, null, $encoding);
}
private static function html_encoding_callback(array $m)
{
$i = 1;
$entities = '';
$m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8'));
while (isset($m[$i])) {
if (0x80 > $m[$i]) {
$entities .= \chr($m[$i++]);
continue;
}
if (0xF0 <= $m[$i]) {
$c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
} elseif (0xE0 <= $m[$i]) {
$c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
} else {
$c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
}
$entities .= '&#'.$c.';';
}
return $entities;
}
private static function title_case(array $s)
{
return self::mb_convert_case($s[1], MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], MB_CASE_LOWER, 'UTF-8');
}
private static function getData($file)
{
if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
return require $file;
}
return false;
}
private static function getEncoding($encoding)
{
if (null === $encoding) {
return self::$internalEncoding;
}
if ('UTF-8' === $encoding) {
return 'UTF-8';
}
$encoding = strtoupper($encoding);
if ('8BIT' === $encoding || 'BINARY' === $encoding) {
return 'CP850';
}
if ('UTF8' === $encoding) {
return 'UTF-8';
}
return $encoding;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Mbstring as p;
if (!function_exists('mb_strlen')) {
define('MB_CASE_UPPER', 0);
define('MB_CASE_LOWER', 1);
define('MB_CASE_TITLE', 2);
function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); }
function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); }
function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); }
function mb_decode_numericentity($s, $convmap, $enc = null) { return p\Mbstring::mb_decode_numericentity($s, $convmap, $enc); }
function mb_encode_numericentity($s, $convmap, $enc = null, $is_hex = false) { return p\Mbstring::mb_encode_numericentity($s, $convmap, $enc, $is_hex); }
function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); }
function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); }
function mb_language($lang = null) { return p\Mbstring::mb_language($lang); }
function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); }
function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); }
function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); }
function mb_parse_str($s, &$result = array()) { parse_str($s, $result); }
function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); }
function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); }
function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); }
function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); }
function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); }
function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); }
function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); }
function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); }
function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); }
function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); }
function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); }
function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); }
function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); }
function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); }
function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); }
function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); }
function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); }
function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); }
function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); }
}
if (!function_exists('mb_chr')) {
function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); }
function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); }
function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); }
}
if (!function_exists('mb_str_split')) {
function mb_str_split($string, $split_length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $split_length, $encoding); }
}
<?php
namespace Consolidation\Config;
trait ConfigAwareTrait
{
/**
* @var ConfigInterface
*/
protected $config;
/**
* Set the config management object.
*
* @param ConfigInterface $config
*
* @return $this
*/
public function setConfig(ConfigInterface $config)
{
$this->config = $config;
return $this;
}
/**
* Get the config management object.
*
* @return ConfigInterface
*/
public function getConfig()
{
return $this->config;
}
}
<?php
namespace Consolidation\Config\Util;
use Consolidation\Config\Config;
use Consolidation\Config\ConfigInterface;
/**
* Provides configuration objects with an 'interpolate' method
* that may be used to inject config values into tokens embedded
* in strings.
*/
interface ConfigInterpolatorInterface extends ConfigInterface
{
/**
* interpolate replaces tokens in a string with the correspnding
* value from this config object. Tokens are surrounded by double
* curley braces, e.g. "{{key}}".
*
* Example:
* If the message is 'Hello, {{user.name}}', then the key user.name
* is fetched from the config object, and the token {{user.name}} is
* replaced with the result.
*
* @param string $message Message containing tokens to be replaced
* @param string|bool $default The value to substitute for tokens that
* are not found in the configuration. If `false`, then missing
* tokens are not replaced.
* @return string
*/
public function interpolate($message, $default = '');
/**
* mustInterpolate works exactly like interpolate, save for the fact
* that an exception is thrown is any of the tokens are not replaced.
*/
public function mustInterpolate($message);
}
<?php
namespace Consolidation\Config\Util;
use Consolidation\Config\Config;
use Consolidation\Config\ConfigInterface;
use Consolidation\Config\Util\ArrayUtil;
use Consolidation\Config\Util\ConfigInterpolatorInterface;
use Consolidation\Config\Util\ConfigInterpolatorTrait;
/**
* Overlay different configuration objects that implement ConfigInterface
* to make a priority-based, merged configuration object.
*
* Note that using a ConfigOverlay hides the defaults stored in each
* individual configuration context. When using overlays, always call
* getDefault / setDefault on the ConfigOverlay object itself.
*/
class ConfigOverlay implements ConfigInterface, ConfigInterpolatorInterface, ConfigRuntimeInterface
{
use ConfigInterpolatorTrait;
protected $contexts = [];
const DEFAULT_CONTEXT = 'default';
const PROCESS_CONTEXT = 'process';
public function __construct()
{
$this->contexts[self::DEFAULT_CONTEXT] = new Config();
$this->contexts[self::PROCESS_CONTEXT] = new Config();
}
/**
* Add a named configuration object to the configuration overlay.
* Configuration objects added LAST have HIGHEST priority, with the
* exception of the fact that the process context always has the
* highest priority.
*
* If a context has already been added, its priority will not change.
*/
public function addContext($name, ConfigInterface $config)
{
$process = $this->contexts[self::PROCESS_CONTEXT];
unset($this->contexts[self::PROCESS_CONTEXT]);
$this->contexts[$name] = $config;
$this->contexts[self::PROCESS_CONTEXT] = $process;
return $this;
}
/**
* Add a placeholder context that will be prioritized higher than
* existing contexts. This is done to ensure that contexts added
* later will maintain a higher priority if the placeholder context
* is later relaced with a different configuration set via addContext().
*
* @param string $name
* @return $this
*/
public function addPlaceholder($name)
{
return $this->addContext($name, new Config());
}
/**
* Increase the priority of the named context such that it is higher
* in priority than any existing context except for the 'process'
* context.
*
* @param string $name
* @return $this
*/
public function increasePriority($name)
{
$config = $this->getContext($name);
unset($this->contexts[$name]);
return $this->addContext($name, $config);
}
public function hasContext($name)
{
return isset($this->contexts[$name]);
}
public function getContext($name)
{
if ($this->hasContext($name)) {
return $this->contexts[$name];
}
return new Config();
}
public function runtimeConfig()
{
return $this->getContext(self::PROCESS_CONTEXT);
}
public function removeContext($name)
{
unset($this->contexts[$name]);
}
/**
* Determine if a non-default config value exists.
*/
public function findContext($key)
{
foreach (array_reverse($this->contexts) as $name => $config) {
if ($config->has($key)) {
return $config;
}
}
return false;
}
/**
* @inheritdoc
*/
public function has($key)
{
return $this->findContext($key) != false;
}
/**
* @inheritdoc
*/
public function get($key, $default = null)
{
if (is_array($default)) {
return $this->getUnion($key);
}
return $this->getSingle($key, $default);
}
public function getSingle($key, $default = null)
{
$context = $this->findContext($key);
if ($context) {
return $context->get($key, $default);
}
return $default;
}
public function getUnion($key)
{
$result = [];
foreach (array_reverse($this->contexts) as $name => $config) {
$item = (array) $config->get($key, []);
if ($item !== null) {
$result = array_merge($result, $item);
}
}
return $result;
}
/**
* @inheritdoc
*/
public function set($key, $value)
{
$this->contexts[self::PROCESS_CONTEXT]->set($key, $value);
return $this;
}
/**
* @inheritdoc
*/
public function import($data)
{
$this->unsupported(__FUNCTION__);
}
/**
* @inheritdoc
*/
public function replace($data)
{
$this->unsupported(__FUNCTION__);
}
/**
* @inheritdoc
*/
public function combine($data)
{
$this->unsupported(__FUNCTION__);
}
/**
* @inheritdoc
*/
protected function unsupported($fn)
{
throw new \Exception("The method '$fn' is not supported for the ConfigOverlay class.");
}
/**
* @inheritdoc
*/
public function export()
{
$export = [];
foreach ($this->contexts as $name => $config) {
$exportToMerge = $config->export();
$export = \array_replace_recursive($export, $exportToMerge);
}
return $export;
}
/**
* exportAll returns the export of all contexts, separated into
* separate buckets keyed by context name.
*
* @return array
*/
public function exportAll()
{
$export = [];
foreach ($this->contexts as $name => $config) {
$exportToInsert = $config->export();
$export[$name] = $exportToInsert;
}
return $export;
}
/**
* @inheritdoc
*/
public function hasDefault($key)
{
return $this->contexts[self::DEFAULT_CONTEXT]->has($key);
}
/**
* @inheritdoc
*/
public function getDefault($key, $default = null)
{
return $this->contexts[self::DEFAULT_CONTEXT]->get($key, $default);
}
/**
* @inheritdoc
*/
public function setDefault($key, $value)
{
$this->contexts[self::DEFAULT_CONTEXT]->set($key, $value);
return $this;
}
}
<?php
namespace Consolidation\Config\Util;
/**
* Fetch a configuration value from a configuration group. If the
* desired configuration value is not found in the most specific
* group named, keep stepping up to the next parent group until a
* value is located.
*
* Given the following constructor inputs:
* - $prefix = "command."
* - $group = "foo.bar.baz"
* - $postfix = ".options."
* Then the `get` method will then consider, in order:
* - command.foo.bar.baz.options
* - command.foo.bar.options
* - command.foo.options
* If any of these contain an option for "$key", then return its value.
*/
abstract class ConfigGroup
{
protected $config;
protected $group;
protected $prefix;
protected $postfix;
public function __construct($config, $group, $prefix = '', $postfix = '.')
{
$this->config = $config;
$this->group = $group;
$this->prefix = $prefix;
$this->postfix = $postfix;
}
/**
* Return a description of the configuration group (with prefix and postfix).
*/
public function describe($property)
{
return $this->prefix . $this->group . $this->postfix . $property;
}
/**
* Get the requested configuration key from the most specific configuration
* group that contains it.
*/
abstract public function get($key);
/**
* Given a group name, such as "foo.bar.baz", return the next configuration
* group in the fallback hierarchy, e.g. "foo.bar".
*/
protected function moreGeneralGroupName($group)
{
$result = preg_replace('#\.[^.]*$#', '', $group);
if ($result != $group) {
return $result;
}
return false;
}
}
<?php
namespace Consolidation\Config\Util;
/**
* Works like 'getWithFallback', but merges results from all applicable
* groups. Settings from most specific group take precedence.
*/
class ConfigMerge extends ConfigGroup
{
/**
* @inheritdoc
*/
public function get($key)
{
return $this->getWithMerge($key, $this->group, $this->prefix, $this->postfix);
}
/**
* Merge available configuration from each configuration group.
*/
public function getWithMerge($key, $group, $prefix = '', $postfix = '.')
{
$configKey = "{$prefix}{$group}${postfix}{$key}";
$result = $this->config->get($configKey, []);
if (!is_array($result)) {
throw new \UnexpectedValueException($configKey . ' must be a list of settings to apply.');
}
$moreGeneralGroupname = $this->moreGeneralGroupName($group);
if ($moreGeneralGroupname) {
$result += $this->getWithMerge($key, $moreGeneralGroupname, $prefix, $postfix);
}
return $result;
}
}
<?php
namespace Consolidation\Config\Util;
/**
* Useful array utilities.
*/
class ArrayUtil
{
/**
* Merges arrays recursively while preserving.
*
* @param array $array1
* @param array $array2
*
* @return array
*
* @see http://php.net/manual/en/function.array-merge-recursive.php#92195
* @see https://github.com/grasmash/bolt/blob/robo-rebase/src/Robo/Common/ArrayManipulator.php#L22
*/
public static function mergeRecursiveDistinct(
array &$array1,
array &$array2
) {
$merged = $array1;
foreach ($array2 as $key => &$value) {
$merged[$key] = self::mergeRecursiveValue($merged, $key, $value);
}
return $merged;
}
/**
* Process the value in an mergeRecursiveDistinct - make a recursive
* call if needed.
*/
protected static function mergeRecursiveValue(&$merged, $key, $value)
{
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
return self::mergeRecursiveDistinct($merged[$key], $value);
}
return $value;
}
/**
* Merges arrays recursively while preserving.
*
* @param array $array1
* @param array $array2
*
* @return array
*
* @see http://php.net/manual/en/function.array-merge-recursive.php#92195
* @see https://github.com/grasmash/bolt/blob/robo-rebase/src/Robo/Common/ArrayManipulator.php#L22
*/
public static function mergeRecursiveSelect(
array &$array1,
array &$array2,
array $selectionList,
$keyPrefix = ''
) {
$merged = $array1;
foreach ($array2 as $key => &$value) {
$merged[$key] = self::mergeRecursiveSelectValue($merged, $key, $value, $selectionList, $keyPrefix);
}
return $merged;
}
/**
* Process the value in an mergeRecursiveDistinct - make a recursive
* call if needed.
*/
protected static function mergeRecursiveSelectValue(&$merged, $key, $value, $selectionList, $keyPrefix)
{
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
if (self::selectMerge($keyPrefix, $key, $selectionList)) {
return array_merge_recursive($merged[$key], $value);
} else {
return self::mergeRecursiveSelect($merged[$key], $value, $selectionList, "${keyPrefix}${key}.");
}
}
return $value;
}
protected static function selectMerge($keyPrefix, $key, $selectionList)
{
return in_array("${keyPrefix}${key}", $selectionList);
}
/**
* Fills all of the leaf-node values of a nested array with the
* provided replacement value.
*/
public static function fillRecursive(array $data, $fill)
{
$result = [];
foreach ($data as $key => $value) {
$result[$key] = $fill;
if (self::isAssociative($value)) {
$result[$key] = self::fillRecursive($value, $fill);
}
}
return $result;
}
/**
* Return true if the provided parameter is an array, and at least
* one key is non-numeric.
*/
public static function isAssociative($testArray)
{
if (!is_array($testArray)) {
return false;
}
foreach (array_keys($testArray) as $key) {
if (!is_numeric($key)) {
return true;
}
}
return false;
}
}
<?php
namespace Consolidation\Config\Util;
use Consolidation\Config\Config;
use Consolidation\Config\ConfigInterface;
/**
* Provides configuration objects with an 'interpolate' method
* that may be used to inject config values into tokens embedded
* in strings..
*/
trait ConfigInterpolatorTrait
{
protected $interpolator;
protected function getInterpolator()
{
if (!isset($this->interpolator)) {
$this->interpolator = new Interpolator();
}
return $this->interpolator;
}
/**
* @inheritdoc
*/
public function interpolate($message, $default = '')
{
return $this->getInterpolator()->interpolate($this, $message, $default);
}
/**
* @inheritdoc
*/
public function mustInterpolate($message)
{
return $this->getInterpolator()->mustInterpolate($this, $message);
}
}
<?php
namespace Consolidation\Config\Util;
use Consolidation\Config\Config;
use Consolidation\Config\ConfigInterface;
/**
* ConfigRutimeInterface provides a method that returns those elements
* of the configuration that were set at runtime, e.g. via commandline
* options rather than being loaded from a file.
*/
interface ConfigRuntimeInterface
{
/**
* runtimeConfig returns those elements of the configuration not
* loaded from a file.
*
* @return ConfigInterface
*/
public function runtimeConfig();
}
<?php
namespace Consolidation\Config\Util;
use Consolidation\Config\Config;
use Consolidation\Config\ConfigInterface;
/**
* Provides configuration objects with an 'interpolate' method
* that may be used to inject config values into tokens embedded
* in strings..
*/
class Interpolator
{
/**
* interpolate replaces tokens in a string with the correspnding
* value from this config object. Tokens are surrounded by double
* curley braces, e.g. "{{key}}".
*
* Example:
* If the message is 'Hello, {{user.name}}', then the key user.name
* is fetched from the config object, and the token {{user.name}} is
* replaced with the result.
*
* @param string $message Message containing tokens to be replaced
* @param string|bool $default The value to substitute for tokens that
* are not found in the configuration. If `false`, then missing
* tokens are not replaced.
* @return string
*/
public function interpolate($data, $message, $default = '')
{
$replacements = $this->replacements($data, $message, $default);
return strtr($message, $replacements);
}
/**
* @inheritdoc
*/
public function mustInterpolate($data, $message)
{
$result = $this->interpolate($data, $message, false);
$tokens = $this->findTokens($result);
if (!empty($tokens)) {
throw new \Exception('The following required keys were not found in configuration: ' . implode(',', $tokens));
}
return $result;
}
/**
* findTokens finds all of the tokens in the provided message
*
* @param string $message String with tokens
* @return string[] map of token to key, e.g. {{key}} => key
*/
public function findTokens($message)
{
if (!preg_match_all('#{{([a-zA-Z0-9._-]+)}}#', $message, $matches, PREG_SET_ORDER)) {
return [];
}
$tokens = [];
foreach ($matches as $matchSet) {
list($sourceText, $key) = $matchSet;
$tokens[$sourceText] = $key;
}
return $tokens;
}
/**
* Replacements looks up all of the replacements in the configuration
* object, given the token keys from the provided message. Keys that
* do not exist in the configuration are replaced with the default value.
*/
public function replacements($data, $message, $default = '')
{
$tokens = $this->findTokens($message);
$replacements = [];
foreach ($tokens as $sourceText => $key) {
$replacementText = $this->get($data, $key, $default);
if ($replacementText !== false) {
$replacements[$sourceText] = $replacementText;
}
}
return $replacements;
}
protected function get($data, $key, $default)
{
if (is_array($data)) {
return array_key_exists($key, $data) ? $data[$key] : $default;
}
if ($data instanceof ConfigInterface) {
return $data->get($key, $default);
}
throw new \Exception('Bad data type provided to Interpolator');
}
}
<?php
namespace Consolidation\Config\Util;
/**
* Fetch a configuration value from a configuration group. If the
* desired configuration value is not found in the most specific
* group named, keep stepping up to the next parent group until a
* value is located.
*
* Given the following constructor inputs:
* - $prefix = "command."
* - $group = "foo.bar.baz"
* - $postfix = ".options."
* Then the `get` method will then consider, in order:
* - command.foo.bar.baz.options
* - command.foo.bar.options
* - command.foo.options
* If any of these contain an option for "$key", then return its value.
*/
class ConfigFallback extends ConfigGroup
{
/**
* @inheritdoc
*/
public function get($key)
{
return $this->getWithFallback($key, $this->group, $this->prefix, $this->postfix);
}
/**
* Fetch an option value from a given key, or, if that specific key does
* not contain a value, then consult various fallback options until a
* value is found.
*
*/
protected function getWithFallback($key, $group, $prefix = '', $postfix = '.')
{
$configKey = "{$prefix}{$group}${postfix}{$key}";
if ($this->config->has($configKey)) {
return $this->config->get($configKey);
}
if ($this->config->hasDefault($configKey)) {
return $this->config->getDefault($configKey);
}
$moreGeneralGroupname = $this->moreGeneralGroupName($group);
if ($moreGeneralGroupname) {
return $this->getWithFallback($key, $moreGeneralGroupname, $prefix, $postfix);
}
return null;
}
}
<?php
namespace Consolidation\Config\Util;
use Consolidation\Config\Config;
use Consolidation\Config\ConfigInterface;
/**
* Provide a configuration object that fetches values from environment
* variables.
*/
class EnvConfig implements ConfigInterface
{
/** @var string */
protected $prefix;
/**
* EnvConfig constructor
*
* @param $prefix The string to appear before every environment
* variable key. For example, if the prefix is 'MYAPP_', then
* the key 'foo.bar' will be fetched from the environment variable
* MYAPP_FOO_BAR.
*/
public function __construct($prefix)
{
// Ensure that the prefix is always uppercase, and always
// ends with a '_', regardless of the form the caller provided.
$this->prefix = strtoupper(rtrim($prefix, '_')) . '_';
}
/**
* @inheritdoc
*/
public function has($key)
{
return $this->get($key) !== null;
}
/**
* @inheritdoc
*/
public function get($key, $defaultFallback = null)
{
$envKey = $this->prefix . strtoupper(strtr($key, '.-', '__'));
$envKey = str_replace($this->prefix . $this->prefix, $this->prefix, $envKey);
return getenv($envKey) ?: $defaultFallback;
}
/**
* @inheritdoc
*/
public function set($key, $value)
{
throw new \Exception('Cannot call "set" on environmental configuration.');
}
/**
* @inheritdoc
*/
public function import($data)
{
// no-op
}
/**
* @inheritdoc
*/
public function export()
{
return [];
}
/**
* @inheritdoc
*/
public function hasDefault($key)
{
return false;
}
/**
* @inheritdoc
*/
public function getDefault($key, $defaultFallback = null)
{
return $defaultFallback;
}
/**
* @inheritdoc
*/
public function setDefault($key, $value)
{
throw new \Exception('Cannot call "setDefault" on environmental configuration.');
}
}
<?php
namespace Consolidation\Config\Inject;
use Consolidation\Config\Util\ConfigMerge;
/**
* Given an object that contains configuration methods, inject any
* configuration found in the configuration file.
*
* The proper use for this method is to call setter methods of the
* provided object. Using configuration to call methods that do work
* is an abuse of this mechanism.
*/
class ConfigForSetters
{
protected $config;
public function __construct($config, $group, $prefix = '', $postfix = '')
{
if (!empty($group) && empty($postfix)) {
$postfix = '.';
}
$this->config = new ConfigMerge($config, $group, $prefix, $postfix);
}
public function apply($object, $configurationKey)
{
$settings = $this->config->get($configurationKey);
foreach ($settings as $setterMethod => $args) {
$fn = [$object, $setterMethod];
if (is_callable($fn)) {
$result = call_user_func_array($fn, (array)$args);
// We require that $fn must only be used with setter methods.
// Setter methods are required to always return $this so that
// they may be chained. We will therefore throw an exception
// for any setter that returns something else.
if ($result != $object) {
$methodDescription = get_class($object) . "::$setterMethod";
$propertyDescription = $this->config->describe($configurationKey);
throw new \Exception("$methodDescription did not return '\$this' when processing $propertyDescription.");
}
}
}
}
}
<?php
namespace Consolidation\Config\Inject;
use Consolidation\Config\ConfigInterface;
use Consolidation\Config\Util\ConfigFallback;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputOption;
class ConfigForCommand implements EventSubscriberInterface
{
protected $config;
protected $application;
public function __construct(ConfigInterface $config)
{
$this->config = $config;
}
public function setApplication(Application $application)
{
$this->application = $application;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [ConsoleEvents::COMMAND => 'injectConfiguration'];
}
/**
* Before a Console command runs, inject configuration settings
* for this command into the default value of the options of
* this command.
*
* @param \Symfony\Component\Console\Event\ConsoleCommandEvent $event
*/
public function injectConfiguration(ConsoleCommandEvent $event)
{
$command = $event->getCommand();
$this->injectConfigurationForGlobalOptions($event->getInput());
$this->injectConfigurationForCommand($command, $event->getInput());
$targetOfHelpCommand = $this->getHelpCommandTarget($command, $event->getInput());
if ($targetOfHelpCommand) {
$this->injectConfigurationForCommand($targetOfHelpCommand, $event->getInput());
}
}
protected function injectConfigurationForGlobalOptions($input)
{
if (!$this->application) {
return;
}
$configGroup = new ConfigFallback($this->config, 'options');
$definition = $this->application->getDefinition();
$options = $definition->getOptions();
return $this->injectConfigGroupIntoOptions($configGroup, $options, $input);
}
protected function injectConfigurationForCommand($command, $input)
{
$commandName = $command->getName();
$commandName = str_replace(':', '.', $commandName);
$configGroup = new ConfigFallback($this->config, $commandName, 'command.', '.options.');
$definition = $command->getDefinition();
$options = $definition->getOptions();
return $this->injectConfigGroupIntoOptions($configGroup, $options, $input);
}
protected function injectConfigGroupIntoOptions($configGroup, $options, $input)
{
foreach ($options as $option => $inputOption) {
$key = str_replace('.', '-', $option);
$value = $configGroup->get($key);
if ($value !== null) {
if (is_bool($value) && ($value == true)) {
$input->setOption($key, $value);
} elseif ($inputOption->acceptValue()) {
$inputOption->setDefault($value);
}
}
}
}
protected function getHelpCommandTarget($command, $input)
{
if (($command->getName() != 'help') || (!isset($this->application))) {
return false;
}
$this->fixInputForSymfony2($command, $input);
// Symfony Console helpfully swaps 'command_name' and 'command'
// depending on whether the user entered `help foo` or `--help foo`.
// One of these is always `help`, and the other is the command we
// are actually interested in.
$nameOfCommandToDescribe = $input->getArgument('command_name');
if ($nameOfCommandToDescribe == 'help') {
$nameOfCommandToDescribe = $input->getArgument('command');
}
return $this->application->find($nameOfCommandToDescribe);
}
protected function fixInputForSymfony2($command, $input)
{
// Symfony 3.x prepares $input for us; Symfony 2.x, on the other
// hand, passes it in prior to binding with the command definition,
// so we have to go to a little extra work. It may be inadvisable
// to do these steps for commands other than 'help'.
if (!$input->hasArgument('command_name')) {
$command->ignoreValidationErrors();
$command->mergeApplicationDefinition();
$input->bind($command->getDefinition());
}
}
}
<?php
namespace Consolidation\Config;
interface ConfigInterface
{
/**
* Determine if a non-default config value exists.
*/
public function has($key);
/**
* Fetch a configuration value
*
* @param string $key Which config item to look up
* @param string|null $defaultFallback Fallback default value to use when
* configuration object has neither a value nor a default. Use is
* discouraged; use default context in ConfigOverlay, or provide defaults
* using a config processor.
*
* @return mixed
*/
public function get($key, $defaultFallback = null);
/**
* Set a config value
*
* @param string $key
* @param mixed $value
*
* @return $this
*/
public function set($key, $value);
/**
* Import configuration from the provided nexted array, replacing whatever
* was here previously. No processing is done on the provided data.
*
* @deprecated Use 'replace'. Dflydev\DotAccessData\Data::import() merges, which is confusing, since this method replaces.
*
* @param array $data
* @return Config
*/
public function import($data);
/**
* Load configuration from the provided nexted array, replacing whatever
* was here previously. No processing is done on the provided data.
*
* TODO: This will become a required method in version 2.0. Adding now
* would break clients that implement ConfigInterface.
*
* @param array $data
* @return Config
*/
// public function replace($data);
/**
* Import configuration from the provided nexted array, merging with whatever
* was here previously. No processing is done on the provided data.
* Any data provided to the combine() method will overwrite existing data
* with the same key.
*
* TODO: This will become a required method in version 2.0. Adding now
* would break clients that implement ConfigInterface.
*
* @param array $data
* @return Config
*/
// public function combine($data);
/**
* Export all configuration as a nested array.
*/
public function export();
/**
* Return the default value for a given configuration item.
*
* @param string $key
*
* @return mixed
*/
public function hasDefault($key);
/**
* Return the default value for a given configuration item.
*
* @param string $key
* @param mixed $defaultFallback
*
* @return mixed
*/
public function getDefault($key, $defaultFallback = null);
/**
* Set the default value for a configuration setting. This allows us to
* set defaults either before or after more specific configuration values
* are loaded. Keeping defaults separate from current settings also
* allows us to determine when a setting has been overridden.
*
* @param string $key
* @param string $value
*/
public function setDefault($key, $value);
}
<?php
namespace Consolidation\Config;
use Dflydev\DotAccessData\Data;
use Consolidation\Config\Util\ConfigInterpolatorInterface;
use Consolidation\Config\Util\ConfigInterpolatorTrait;
class Config implements ConfigInterface, ConfigInterpolatorInterface
{
use ConfigInterpolatorTrait;
/**
* @var Data
*/
protected $config;
/**
* TODO: make this private in 2.0 to prevent being saved as an array
* Making private now breaks backward compatibility
*
* @var Data
*/
protected $defaults;
/**
* Create a new configuration object, and initialize it with
* the provided nested array containing configuration data.
*
* @param array $data - Config data to store
*/
public function __construct(array $data = null)
{
$this->config = new Data($data);
$this->setDefaults(new Data());
}
/**
* {@inheritdoc}
*/
public function has($key)
{
return ($this->config->has($key));
}
/**
* {@inheritdoc}
*/
public function get($key, $defaultFallback = null)
{
if ($this->has($key)) {
return $this->config->get($key);
}
return $this->getDefault($key, $defaultFallback);
}
/**
* {@inheritdoc}
*/
public function set($key, $value)
{
$this->config->set($key, $value);
return $this;
}
/**
* {@inheritdoc}
*/
public function import($data)
{
return $this->replace($data);
}
/**
* {@inheritdoc}
*/
public function replace($data)
{
$this->config = new Data($data);
return $this;
}
/**
* {@inheritdoc}
*/
public function combine($data)
{
if (!empty($data)) {
$this->config->import($data, true);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function export()
{
return $this->config->export();
}
/**
* {@inheritdoc}
*/
public function hasDefault($key)
{
return $this->getDefaults()->has($key);
}
/**
* {@inheritdoc}
*/
public function getDefault($key, $defaultFallback = null)
{
return $this->hasDefault($key) ? $this->getDefaults()->get($key) : $defaultFallback;
}
/**
* {@inheritdoc}
*/
public function setDefault($key, $value)
{
$this->getDefaults()->set($key, $value);
return $this;
}
/**
* Return the class $defaults property and ensure it's a Data object
* TODO: remove Data object validation in 2.0
*
* @return Data
*/
protected function getDefaults()
{
// Ensure $this->defaults is a Data object (not an array)
if (!$this->defaults instanceof Data) {
$this->setDefaults($this->defaults);
}
return $this->defaults;
}
/**
* Sets the $defaults class parameter
* TODO: remove support for array in 2.0 as this would currently break backward compatibility
*
* @param Data|array $defaults
*
* @throws \Exception
*/
protected function setDefaults($defaults)
{
if (is_array($defaults)) {
$this->defaults = new Data($defaults);
} elseif ($defaults instanceof Data) {
$this->defaults = $defaults;
} else {
throw new \Exception("Unknown type provided for \$defaults");
}
}
}
<?php
namespace Consolidation\Config\Loader;
/**
* Load configuration files, and fill in any property values that
* need to be expanded.
*/
interface ConfigLoaderInterface
{
/**
* Convert loaded configuration into a simple php nested array.
*
* @return array
*/
public function export();
/**
* Return the top-level keys in the exported data.
*
* @return array
*/
public function keys();
/**
* Return a symbolic name for this configuration loader instance.
*/
public function getSourceName();
}
<?php
namespace Consolidation\Config\Loader;
/**
* Load configuration files.
*/
abstract class ConfigLoader implements ConfigLoaderInterface
{
protected $config = [];
protected $source = '';
public function getSourceName()
{
return $this->source;
}
protected function setSourceName($source)
{
$this->source = $source;
return $this;
}
public function export()
{
return $this->config;
}
public function keys()
{
return array_keys($this->config);
}
abstract public function load($path);
}
<?php
namespace Consolidation\Config\Loader;
use Grasmash\Expander\Expander;
use Consolidation\Config\Util\ArrayUtil;
/**
* The config processor combines multiple configuration
* files together, and processes them as necessary.
*/
class ConfigProcessor
{
protected $processedConfig = [];
protected $unprocessedConfig = [];
protected $nameOfItemsToMerge = [];
protected $expander;
public function __construct($expander = null)
{
$this->expander = $expander ?: new Expander();
}
/**
* By default, string config items always REPLACE, not MERGE when added
* from different sources. This method will allow applications to alter
* this behavior for specific items so that strings from multiple sources
* will be merged together into an array instead.
*/
public function useMergeStrategyForKeys($itemName)
{
if (is_array($itemName)) {
$this->nameOfItemsToMerge = array_merge($this->nameOfItemsToMerge, $itemName);
return $this;
}
$this->nameOfItemsToMerge[] = $itemName;
return $this;
}
/**
* Extend the configuration to be processed with the
* configuration provided by the specified loader.
*
* @param ConfigLoaderInterface $loader
*/
public function extend(ConfigLoaderInterface $loader)
{
return $this->addFromSource($loader->export(), $loader->getSourceName());
}
/**
* Extend the configuration to be processed with
* the provided nested array.
*
* @param array $data
*/
public function add($data)
{
$this->unprocessedConfig[] = $data;
return $this;
}
/**
* Extend the configuration to be processed with
* the provided nested array. Also record the name
* of the data source, if applicable.
*
* @param array $data
* @param string $source
*/
protected function addFromSource($data, $source = '')
{
if (empty($source)) {
return $this->add($data);
}
$this->unprocessedConfig[$source] = $data;
return $this;
}
/**
* Process all of the configuration that has been collected,
* and return a nested array.
*
* @return array
*/
public function export($referenceArray = [])
{
if (!empty($this->unprocessedConfig)) {
$this->processedConfig = $this->process(
$this->processedConfig,
$this->fetchUnprocessed(),
$referenceArray
);
}
return $this->processedConfig;
}
/**
* To aid in debugging: return the source of each configuration item.
* n.b. Must call this function *before* export and save the result
* if persistence is desired.
*/
public function sources()
{
$sources = [];
foreach ($this->unprocessedConfig as $sourceName => $config) {
if (!empty($sourceName)) {
$configSources = ArrayUtil::fillRecursive($config, $sourceName);
$sources = ArrayUtil::mergeRecursiveDistinct($sources, $configSources);
}
}
return $sources;
}
/**
* Get the configuration to be processed, and clear out the
* 'unprocessed' list.
*
* @return array
*/
protected function fetchUnprocessed()
{
$toBeProcessed = $this->unprocessedConfig;
$this->unprocessedConfig = [];
return $toBeProcessed;
}
/**
* Use a map-reduce to evaluate the items to be processed,
* and merge them into the processed array.
*
* @param array $processed
* @param array $toBeProcessed
* @return array
*/
protected function process(array $processed, array $toBeProcessed, $referenceArray = [])
{
$toBeReduced = array_map([$this, 'preprocess'], $toBeProcessed);
$reduced = array_reduce($toBeReduced, [$this, 'reduceOne'], $processed);
return $this->evaluate($reduced, $referenceArray);
}
/**
* Process a single configuration file from the 'to be processed'
* list. By default this is a no-op. Override this method to
* provide any desired configuration preprocessing, e.g. dot-notation
* expansion of the configuration keys, etc.
*
* @param array $config
* @return array
*/
protected function preprocess(array $config)
{
return $config;
}
/**
* Evaluate one item in the 'to be evaluated' list, and then
* merge it into the processed configuration (the 'carry').
*
* @param array $processed
* @param array $config
* @return array
*/
protected function reduceOne(array $processed, array $config)
{
return ArrayUtil::mergeRecursiveSelect($processed, $config, $this->nameOfItemsToMerge);
}
/**
* Evaluate one configuration item.
*
* @param array $processed
* @param array $config
* @return array
*/
protected function evaluate(array $config, $referenceArray = [])
{
return $this->expander->expandArrayProperties(
$config,
$referenceArray
);
}
}
<?php
namespace Consolidation\Config\Loader;
use Symfony\Component\Yaml\Yaml;
/**
* Load configuration files, and fill in any property values that
* need to be expanded.
*/
class YamlConfigLoader extends ConfigLoader
{
public function load($path)
{
$this->setSourceName($path);
// We silently skip any nonexistent config files, so that
// clients may simply `load` all of their candidates.
if (!file_exists($path)) {
$this->config = [];
return $this;
}
$this->config = (array) Yaml::parse(file_get_contents($path));
return $this;
}
}
<?php
namespace Consolidation\Config;
interface GlobalOptionDefaultValuesInterface
{
/**
* Return an associative array of option-key => default-value
*/
public function getGlobalOptionDefaultValues();
}
<?php
namespace Consolidation\Config;
interface ConfigAwareInterface
{
/**
* Set the config reference
*
* @param ConfigInterface $config
*
* @return $this
*/
public function setConfig(ConfigInterface $config);
/**
* Get the config reference
*
* @return ConfigInterface
*/
public function getConfig();
}
<?php
namespace Consolidation\AnnotatedCommand;
/**
* Return a CommandError as the result of a command to pass a status
* code and error message to be displayed.
*
* @package Consolidation\AnnotatedCommand
*/
class CommandError implements ExitCodeInterface, OutputDataInterface
{
protected $message;
protected $exitCode;
public function __construct($message = null, $exitCode = 1)
{
$this->message = $message;
// Ensure the exit code is non-zero. The exit code may have
// come from an exception, and those often default to zero if
// a specific value is not provided.
$this->exitCode = $exitCode == 0 ? 1 : $exitCode;
}
public function getExitCode()
{
return $this->exitCode;
}
public function getOutputData()
{
return $this->message;
}
}
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ReplaceCommandHookDispatcher;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Consolidation\OutputFormatters\FormatterManager;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Options\PrepareFormatter;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\InitializeHookDispatcher;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\OptionsHookDispatcher;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\InteractHookDispatcher;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ValidateHookDispatcher;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ProcessResultHookDispatcher;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\StatusDeterminerHookDispatcher;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ExtracterHookDispatcher;
/**
* Process a command, including hooks and other callbacks.
* There should only be one command processor per application.
* Provide your command processor to the AnnotatedCommandFactory
* via AnnotatedCommandFactory::setCommandProcessor().
*/
class CommandProcessor implements LoggerAwareInterface
{
use LoggerAwareTrait;
/** @var HookManager */
protected $hookManager;
/** @var FormatterManager */
protected $formatterManager;
/** @var PrepareFormatterOptions[] */
protected $prepareOptionsList = [];
/** @var boolean */
protected $passExceptions;
/** @var ResultWriter */
protected $resultWriter;
/** @var ParameterInjection */
protected $parameterInjection;
public function __construct(HookManager $hookManager)
{
$this->hookManager = $hookManager;
}
/**
* Return the hook manager
* @return HookManager
*/
public function hookManager()
{
return $this->hookManager;
}
public function resultWriter()
{
if (!$this->resultWriter) {
$this->setResultWriter(new ResultWriter());
}
return $this->resultWriter;
}
public function setResultWriter($resultWriter)
{
$this->resultWriter = $resultWriter;
}
public function parameterInjection()
{
if (!$this->parameterInjection) {
$this->setParameterInjection(new ParameterInjection());
}
return $this->parameterInjection;
}
public function setParameterInjection($parameterInjection)
{
$this->parameterInjection = $parameterInjection;
}
public function addPrepareFormatter(PrepareFormatter $preparer)
{
$this->prepareOptionsList[] = $preparer;
}
public function setFormatterManager(FormatterManager $formatterManager)
{
$this->formatterManager = $formatterManager;
$this->resultWriter()->setFormatterManager($formatterManager);
return $this;
}
public function setDisplayErrorFunction(callable $fn)
{
$this->resultWriter()->setDisplayErrorFunction($fn);
}
/**
* Set a mode to make the annotated command library re-throw
* any exception that it catches while processing a command.
*
* The default behavior in the current (2.x) branch is to catch
* the exception and replace it with a CommandError object that
* may be processed by the normal output processing passthrough.
*
* In the 3.x branch, exceptions will never be caught; they will
* be passed through, as if setPassExceptions(true) were called.
* This is the recommended behavior.
*/
public function setPassExceptions($passExceptions)
{
$this->passExceptions = $passExceptions;
return $this;
}
public function commandErrorForException(\Exception $e)
{
if ($this->passExceptions) {
throw $e;
}
return new CommandError($e->getMessage(), $e->getCode());
}
/**
* Return the formatter manager
* @return FormatterManager
*/
public function formatterManager()
{
return $this->formatterManager;
}
public function initializeHook(
InputInterface $input,
$names,
AnnotationData $annotationData
) {
$initializeDispatcher = new InitializeHookDispatcher($this->hookManager(), $names);
return $initializeDispatcher->initialize($input, $annotationData);
}
public function optionsHook(
AnnotatedCommand $command,
$names,
AnnotationData $annotationData
) {
$optionsDispatcher = new OptionsHookDispatcher($this->hookManager(), $names);
$optionsDispatcher->getOptions($command, $annotationData);
}
public function interact(
InputInterface $input,
OutputInterface $output,
$names,
AnnotationData $annotationData
) {
$interactDispatcher = new InteractHookDispatcher($this->hookManager(), $names);
return $interactDispatcher->interact($input, $output, $annotationData);
}
public function process(
OutputInterface $output,
$names,
$commandCallback,
CommandData $commandData
) {
$result = [];
try {
$result = $this->validateRunAndAlter(
$names,
$commandCallback,
$commandData
);
return $this->handleResults($output, $names, $result, $commandData);
} catch (\Exception $e) {
$result = $this->commandErrorForException($e);
return $this->handleResults($output, $names, $result, $commandData);
}
}
public function validateRunAndAlter(
$names,
$commandCallback,
CommandData $commandData
) {
// Validators return any object to signal a validation error;
// if the return an array, it replaces the arguments.
$validateDispatcher = new ValidateHookDispatcher($this->hookManager(), $names);
$validated = $validateDispatcher->validate($commandData);
if (is_object($validated)) {
return $validated;
}
// Once we have validated the optins, create the formatter options.
$this->createFormatterOptions($commandData);
$replaceDispatcher = new ReplaceCommandHookDispatcher($this->hookManager(), $names);
if ($this->logger) {
$replaceDispatcher->setLogger($this->logger);
}
if ($replaceDispatcher->hasReplaceCommandHook()) {
$commandCallback = $replaceDispatcher->getReplacementCommand($commandData);
}
// Run the command, alter the results, and then handle output and status
$result = $this->runCommandCallback($commandCallback, $commandData);
return $this->processResults($names, $result, $commandData);
}
public function processResults($names, $result, CommandData $commandData)
{
$processDispatcher = new ProcessResultHookDispatcher($this->hookManager(), $names);
return $processDispatcher->process($result, $commandData);
}
/**
* Create a FormatterOptions object for use in writing the formatted output.
* @param CommandData $commandData
* @return FormatterOptions
*/
protected function createFormatterOptions($commandData)
{
$options = $commandData->input()->getOptions();
$formatterOptions = new FormatterOptions($commandData->annotationData()->getArrayCopy(), $options);
foreach ($this->prepareOptionsList as $preparer) {
$preparer->prepare($commandData, $formatterOptions);
}
$commandData->setFormatterOptions($formatterOptions);
return $formatterOptions;
}
/**
* Handle the result output and status code calculation.
*/
public function handleResults(OutputInterface $output, $names, $result, CommandData $commandData)
{
$statusCodeDispatcher = new StatusDeterminerHookDispatcher($this->hookManager(), $names);
$extractDispatcher = new ExtracterHookDispatcher($this->hookManager(), $names);
return $this->resultWriter()->handle($output, $result, $commandData, $statusCodeDispatcher, $extractDispatcher);
}
/**
* Run the main command callback
*/
protected function runCommandCallback($commandCallback, CommandData $commandData)
{
$result = false;
try {
$args = $this->parameterInjection()->args($commandData);
$result = call_user_func_array($commandCallback, $args);
} catch (\Exception $e) {
$result = $this->commandErrorForException($e);
}
return $result;
}
public function injectIntoCommandData($commandData, $injectedClasses)
{
$this->parameterInjection()->injectIntoCommandData($commandData, $injectedClasses);
}
}
<?php
namespace Consolidation\AnnotatedCommand\Options;
use Consolidation\AnnotatedCommand\AnnotatedCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* AlterOptionsCommandEvent is a subscriber to the Command Event
* that looks up any additional options (e.g. from an OPTION_HOOK)
* that should be added to the command. Options need to be added
* in two circumstances:
*
* 1. When 'help' for the command is called, so that the additional
* command options may be listed in the command description.
*
* 2. When the command itself is called, so that option validation
* may be done.
*
* We defer the addition of options until these times so that we
* do not invoke the option hooks for every command on every run
* of the program, and so that we do not need to defer the addition
* of all of the application hooks until after all of the application
* commands have been added. (Hooks may appear in the same command files
* as command implementations; applications may support command file
* plug-ins, and hooks may add options to commands defined in other
* commandfiles.)
*/
class AlterOptionsCommandEvent implements EventSubscriberInterface
{
/** var Application */
protected $application;
public function __construct(Application $application)
{
$this->application = $application;
}
/**
* @param ConsoleCommandEvent $event
*/
public function alterCommandOptions(ConsoleCommandEvent $event)
{
/* @var Command $command */
$command = $event->getCommand();
$input = $event->getInput();
if ($command->getName() == 'help') {
// Symfony 3.x prepares $input for us; Symfony 2.x, on the other
// hand, passes it in prior to binding with the command definition,
// so we have to go to a little extra work. It may be inadvisable
// to do these steps for commands other than 'help'.
if (!$input->hasArgument('command_name')) {
$command->ignoreValidationErrors();
$command->mergeApplicationDefinition();
$input->bind($command->getDefinition());
}
// Symfony Console helpfully swaps 'command_name' and 'command'
// depending on whether the user entered `help foo` or `--help foo`.
// One of these is always `help`, and the other is the command we
// are actually interested in.
$nameOfCommandToDescribe = $event->getInput()->getArgument('command_name');
if ($nameOfCommandToDescribe == 'help') {
$nameOfCommandToDescribe = $event->getInput()->getArgument('command');
}
$commandToDescribe = $this->application->find($nameOfCommandToDescribe);
$this->findAndAddHookOptions($commandToDescribe);
} else {
$this->findAndAddHookOptions($command);
}
}
public function findAndAddHookOptions($command)
{
if (!$command instanceof AnnotatedCommand) {
return;
}
$command->optionsHook();
}
/**
* @{@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [ConsoleEvents::COMMAND => 'alterCommandOptions'];
}
}
<?php
namespace Consolidation\AnnotatedCommand\Options;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Symfony\Component\Console\Input\InputOption;
/**
* Option providers can add options to commands based on the annotations
* present in a command. For example, a command that specifies @fields
* will automatically be given --format and --fields options.
*
* @see AnnotatedCommandFactory::addListener()
* @see HookManager::addOptionHook()
*/
interface AutomaticOptionsProviderInterface
{
/**
* @return InputOption[]
*/
public function automaticOptions(CommandInfo $commandInfo);
}
<?php
namespace Consolidation\AnnotatedCommand\Options;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface PrepareFormatter
{
public function prepare(CommandData $commandData, FormatterOptions $options);
}
<?php
namespace Consolidation\AnnotatedCommand\Options;
use Symfony\Component\Console\Application;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
class PrepareTerminalWidthOption implements PrepareFormatter
{
/** var Application */
protected $application;
protected $terminal;
/** var int */
protected $defaultWidth;
/** var int */
protected $maxWidth = PHP_INT_MAX;
/** var int */
protected $minWidth = 0;
/* var boolean */
protected $shouldWrap = true;
public function __construct($defaultWidth = 0)
{
$this->defaultWidth = $defaultWidth;
}
public function setApplication(Application $application)
{
$this->application = $application;
}
public function setTerminal($terminal)
{
$this->terminal = $terminal;
}
public function getTerminal()
{
if (!$this->terminal && class_exists('\Symfony\Component\Console\Terminal')) {
$this->terminal = new \Symfony\Component\Console\Terminal();
}
return $this->terminal;
}
public function enableWrap($shouldWrap)
{
$this->shouldWrap = $shouldWrap;
}
public function prepare(CommandData $commandData, FormatterOptions $options)
{
$width = $this->getTerminalWidth();
if (!$width) {
$width = $this->defaultWidth;
}
// Enforce minimum and maximum widths
$width = min($width, $this->getMaxWidth($commandData));
$width = max($width, $this->getMinWidth($commandData));
$options->setWidth($width);
}
protected function getTerminalWidth()
{
// Don't wrap if wrapping has been disabled.
if (!$this->shouldWrap) {
return 0;
}
$terminal = $this->getTerminal();
if ($terminal) {
return $terminal->getWidth();
}
return $this->getTerminalWidthViaApplication();
}
protected function getTerminalWidthViaApplication()
{
if (!$this->application) {
return 0;
}
$dimensions = $this->application->getTerminalDimensions();
if ($dimensions[0] == null) {
return 0;
}
return $dimensions[0];
}
protected function getMaxWidth(CommandData $commandData)
{
return $this->maxWidth;
}
protected function getMinWidth(CommandData $commandData)
{
return $this->minWidth;
}
}
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Parser\Internal\CsvUtils;
class AnnotationData extends \ArrayObject
{
public function get($key, $default = '')
{
return $this->has($key) ? CsvUtils::toString($this[$key]) : $default;
}
public function getList($key, $default = [])
{
return $this->has($key) ? CsvUtils::toList($this[$key]) : $default;
}
public function has($key)
{
return isset($this[$key]);
}
public function keys()
{
return array_keys($this->getArrayCopy());
}
public function set($key, $value = '')
{
$this->offsetSet($key, $value);
return $this;
}
public function append($key, $value = '')
{
$data = $this->offsetGet($key);
if (is_array($data)) {
$this->offsetSet($key, array_merge($data, $value));
} elseif (is_scalar($data)) {
$this->offsetSet($key, $data . $value);
}
return $this;
}
}
<?php
namespace Consolidation\AnnotatedCommand\Cache;
/**
* Make a generic cache object conform to our expected interface.
*/
class CacheWrapper implements SimpleCacheInterface
{
protected $dataStore;
public function __construct($dataStore)
{
$this->dataStore = $dataStore;
}
/**
* Test for an entry from the cache
* @param string $key
* @return boolean
*/
public function has($key)
{
if (method_exists($this->dataStore, 'has')) {
return $this->dataStore->has($key);
}
$test = $this->dataStore->get($key);
return !empty($test);
}
/**
* Get an entry from the cache
* @param string $key
* @return array
*/
public function get($key)
{
return (array) $this->dataStore->get($key);
}
/**
* Store an entry in the cache
* @param string $key
* @param array $data
*/
public function set($key, $data)
{
$this->dataStore->set($key, $data);
}
}
<?php
namespace Consolidation\AnnotatedCommand\Cache;
/**
* Documentation interface.
*
* Clients that use AnnotatedCommandFactory::setDataStore()
* are encouraged to provide a data store that implements
* this interface.
*
* This is not currently required to allow clients to use a generic cache
* store that does not itself depend on the annotated-command library.
* This might be required in a future version.
*/
interface SimpleCacheInterface
{
/**
* Test for an entry from the cache
* @param string $key
* @return boolean
*/
public function has($key);
/**
* Get an entry from the cache
* @param string $key
* @return array
*/
public function get($key);
/**
* Store an entry in the cache
* @param string $key
* @param array $data
*/
public function set($key, $data);
}
<?php
namespace Consolidation\AnnotatedCommand\Cache;
/**
* An empty cache that never stores or fetches any objects.
*/
class NullCache implements SimpleCacheInterface
{
/**
* Test for an entry from the cache
* @param string $key
* @return boolean
*/
public function has($key)
{
return false;
}
/**
* Get an entry from the cache
* @param string $key
* @return array
*/
public function get($key)
{
return [];
}
/**
* Store an entry in the cache
* @param string $key
* @param array $data
*/
public function set($key, $data)
{
}
}
<?php
namespace Consolidation\AnnotatedCommand;
/**
* If an annotated command method encounters an error, then it
* should either throw an exception, or return a result object
* that implements ExitCodeInterface.
*/
interface ExitCodeInterface
{
public function getExitCode();
}
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CommandData
{
/** var AnnotationData */
protected $annotationData;
/** var InputInterface */
protected $input;
/** var OutputInterface */
protected $output;
/** var boolean */
protected $includeOptionsInArgs;
/** var array */
protected $specialDefaults = [];
/** @var string[] */
protected $injectedInstances = [];
/** @var FormatterOptions */
protected $formatterOptions;
public function __construct(
AnnotationData $annotationData,
InputInterface $input,
OutputInterface $output
) {
$this->annotationData = $annotationData;
$this->input = $input;
$this->output = $output;
$this->includeOptionsInArgs = true;
}
/**
* For internal use only; inject an instance to be passed back
* to the command callback as a parameter.
*/
public function injectInstance($injectedInstance)
{
array_unshift($this->injectedInstances, $injectedInstance);
return $this;
}
/**
* Provide a reference to the instances that will be added to the
* beginning of the parameter list when the command callback is invoked.
*/
public function injectedInstances()
{
return $this->injectedInstances;
}
/**
* For backwards-compatibility mode only: disable addition of
* options on the end of the arguments list.
*/
public function setIncludeOptionsInArgs($includeOptionsInArgs)
{
$this->includeOptionsInArgs = $includeOptionsInArgs;
return $this;
}
public function annotationData()
{
return $this->annotationData;
}
public function formatterOptions()
{
return $this->formatterOptions;
}
public function setFormatterOptions($formatterOptions)
{
$this->formatterOptions = $formatterOptions;
}
public function input()
{
return $this->input;
}
public function output()
{
return $this->output;
}
public function arguments()
{
return $this->input->getArguments();
}
public function options()
{
// We cannot tell the difference between '--foo' (an option without
// a value) and the absence of '--foo' when the option has an optional
// value, and the current vallue of the option is 'null' using only
// the public methods of InputInterface. We'll try to figure out
// which is which by other means here.
$options = $this->getAdjustedOptions();
// Make two conversions here:
// --foo=0 wil convert $value from '0' to 'false' for binary options.
// --foo with $value of 'true' will be forced to 'false' if --no-foo exists.
foreach ($options as $option => $value) {
if ($this->shouldConvertOptionToFalse($options, $option, $value)) {
$options[$option] = false;
}
}
return $options;
}
/**
* Use 'hasParameterOption()' to attempt to disambiguate option states.
*/
protected function getAdjustedOptions()
{
$options = $this->input->getOptions();
// If Input isn't an ArgvInput, then return the options as-is.
if (!$this->input instanceof ArgvInput) {
return $options;
}
// If we have an ArgvInput, then we can determine if options
// are missing from the command line. If the option value is
// missing from $input, then we will keep the value `null`.
// If it is present, but has no explicit value, then change it its
// value to `true`.
foreach ($options as $option => $value) {
if (($value === null) && ($this->input->hasParameterOption("--$option"))) {
$options[$option] = true;
}
}
return $options;
}
protected function shouldConvertOptionToFalse($options, $option, $value)
{
// If the value is 'true' (e.g. the option is '--foo'), then convert
// it to false if there is also an option '--no-foo'. n.b. if the
// commandline has '--foo=bar' then $value will not be 'true', and
// --no-foo will be ignored.
if ($value === true) {
// Check if the --no-* option exists. Note that none of the other
// alteration apply in the $value == true case, so we can exit early here.
$negation_key = 'no-' . $option;
return array_key_exists($negation_key, $options) && $options[$negation_key];
}
// If the option is '--foo=0', convert the '0' to 'false' when appropriate.
if ($value !== '0') {
return false;
}
// The '--foo=0' convertion is only applicable when the default value
// is not in the special defaults list. i.e. you get a literal '0'
// when your default is a string.
return in_array($option, $this->specialDefaults);
}
public function cacheSpecialDefaults($definition)
{
foreach ($definition->getOptions() as $option => $inputOption) {
$defaultValue = $inputOption->getDefault();
if (($defaultValue === null) || ($defaultValue === true)) {
$this->specialDefaults[] = $option;
}
}
}
public function getArgsWithoutAppName()
{
$args = $this->arguments();
// When called via the Application, the first argument
// will be the command name. The Application alters the
// input definition to match, adding a 'command' argument
// to the beginning.
if ($this->input->hasArgument('command')) {
array_shift($args);
}
return $args;
}
public function getArgsAndOptions()
{
// Get passthrough args, and add the options on the end.
$args = $this->getArgsWithoutAppName();
if ($this->includeOptionsInArgs) {
$args['options'] = $this->options();
}
return $args;
}
}
<?php
namespace Consolidation\AnnotatedCommand\Input;
/**
* StdinAwareInterface should be implemented by classes that read from
* standard input. This class contains facilities to redirect stdin to
* instead read from a file, e.g. in response to an option or argument
* value.
*
* Using StdinAwareInterface is preferable to reading from php://stdin
* directly, as it provides a mechanism to instead inject an instance
* of StdinHandler that reads from a file, e.g. in tests.
*
* n.b. If the standard input handler is fetched prior to any code
* injecting an stdin handler, you will get an object that is configured
* to read from php://stdin.
*/
interface StdinAwareInterface
{
/**
* Sets the standard input handler.
*
* @param StdinHandler
*/
public function setStdinHandler(StdinHandler $stdin);
/**
* Returns the standard input handler.
*
* @return StdinHandler
*/
public function stdin();
}
<?php
namespace Consolidation\AnnotatedCommand\Input;
/**
* StdinAwareTrait provides the implementation for StdinAwareInterface.
*/
trait StdinAwareTrait
{
protected $stdinHandler;
/**
* @inheritdoc
*/
public function setStdinHandler(StdinHandler $stdin)
{
$this->stdinHandler = $stdin;
}
/**
* @inheritdoc
*/
public function stdin()
{
if (!$this->stdinHandler) {
$this->stdinHandler = new StdinHandler();
}
return $this->stdinHandler;
}
}
<?php
namespace Consolidation\AnnotatedCommand\Input;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Input\InputInterface;
/**
* StdinHandler is a thin wrapper around php://stdin. It provides
* methods for redirecting input from a file, possibly conditionally
* under the control of an Input object.
*
* Example trivial usage (always reads from stdin):
*
* class Example implements StdinAwareInterface
* {
* /**
* * @command cat
* * @param string $file
* * @default $file -
* * /
* public function cat()
* {
* print($this->stdin()->contents());
* }
* }
*
* Command that reads from stdin or file via an option:
*
* /**
* * @command cat
* * @param string $file
* * @default $file -
* * /
* public function cat(InputInterface $input)
* {
* $data = $this->stdin()->select($input, 'file')->contents();
* }
*
* Command that reads from stdin or file via an option:
*
* /**
* * @command cat
* * @option string $file
* * @default $file -
* * /
* public function cat(InputInterface $input)
* {
* $data = $this->stdin()->select($input, 'file')->contents();
* }
*
* It is also possible to inject the selected stream into the input object,
* e.g. if you want the contents of the source file to be fed to any Question
* helper et. al. that the $input object is used with.
*
* /**
* * @command example
* * @option string $file
* * @default $file -
* * /
* public function example(InputInterface $input)
* {
* $this->stdin()->setStream($input, 'file');
* }
*
*
* Inject an alternate source for standard input in tests. Presumes that
* the object under test gets a reference to the StdinHandler via dependency
* injection from the container.
*
* $container->get('stdinHandler')->redirect($pathToTestStdinFileFixture);
*
* You may also inject your stdin file fixture stream into the $input object
* as usual, and then use it with 'select()' or 'setStream()' as shown above.
*
* Finally, this class may also be used in absence of a dependency injection
* container by using the static 'selectStream()' method:
*
* /**
* * @command example
* * @option string $file
* * @default $file -
* * /
* public function example(InputInterface $input)
* {
* $data = StdinHandler::selectStream($input, 'file')->contents();
* }
*
* To test a method that uses this technique, simply inject your stdin
* fixture into the $input object in your test:
*
* $input->setStream(fopen($pathToFixture, 'r'));
*/
class StdinHandler
{
protected $path;
protected $stream;
public static function selectStream(InputInterface $input, $optionOrArg)
{
$handler = new self();
return $handler->setStream($input, $optionOrArg);
}
/**
* hasPath returns 'true' if the stdin handler has a path to a file.
*
* @return bool
*/
public function hasPath()
{
// Once the stream has been opened, we mask the existence of the path.
return !$this->hasStream() && !empty($this->path);
}
/**
* hasStream returns 'true' if the stdin handler has opened a stream.
*
* @return bool
*/
public function hasStream()
{
return !empty($this->stream);
}
/**
* path returns the path to any file that was set as a redirection
* source, or `php://stdin` if none have been.
*
* @return string
*/
public function path()
{
return $this->path ?: 'php://stdin';
}
/**
* close closes the input stream if it was opened.
*/
public function close()
{
if ($this->hasStream()) {
fclose($this->stream);
$this->stream = null;
}
return $this;
}
/**
* redirect specifies a path to a file that should serve as the
* source to read from. If the input path is '-' or empty,
* then output will be taken from php://stdin (or whichever source
* was provided via the 'redirect' method).
*
* @return $this
*/
public function redirect($path)
{
if ($this->pathProvided($path)) {
$this->path = $path;
}
return $this;
}
/**
* select chooses the source of the input stream based on whether or
* not the user provided the specified option or argument on the commandline.
* Stdin is selected if there is no user selection.
*
* @param InputInterface $input
* @param string $optionOrArg
* @return $this
*/
public function select(InputInterface $input, $optionOrArg)
{
$this->redirect($this->getOptionOrArg($input, $optionOrArg));
if (!$this->hasPath() && ($input instanceof StreamableInputInterface)) {
$this->stream = $input->getStream();
}
return $this;
}
/**
* getStream opens and returns the stdin stream (or redirect file).
*/
public function getStream()
{
if (!$this->hasStream()) {
$this->stream = fopen($this->path(), 'r');
}
return $this->stream;
}
/**
* setStream functions like 'select', and also sets up the $input
* object to read from the selected input stream e.g. when used
* with a question helper.
*/
public function setStream(InputInterface $input, $optionOrArg)
{
$this->select($input, $optionOrArg);
if ($input instanceof StreamableInputInterface) {
$stream = $this->getStream();
$input->setStream($stream);
}
return $this;
}
/**
* contents reads the entire contents of the standard input stream.
*
* @return string
*/
public function contents()
{
// Optimization: use file_get_contents if we have a path to a file
// and the stream has not been opened yet.
if (!$this->hasStream()) {
return file_get_contents($this->path());
}
$stream = $this->getStream();
stream_set_blocking($stream, false); // TODO: We did this in backend invoke. Necessary here?
$contents = stream_get_contents($stream);
$this->close();
return $contents;
}
/**
* Returns 'true' if a path was specfied, and that path was not '-'.
*/
protected function pathProvided($path)
{
return !empty($path) && ($path != '-');
}
protected function getOptionOrArg(InputInterface $input, $optionOrArg)
{
if ($input->hasOption($optionOrArg)) {
return $input->getOption($optionOrArg);
}
return $input->getArgument($optionOrArg);
}
}
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ReplaceCommandHookDispatcher;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Consolidation\OutputFormatters\FormatterManager;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Options\PrepareFormatter;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\InitializeHookDispatcher;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\OptionsHookDispatcher;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\InteractHookDispatcher;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ValidateHookDispatcher;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ProcessResultHookDispatcher;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\StatusDeterminerHookDispatcher;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\ExtracterHookDispatcher;
/**
* Write the results of a command. Inject your ResultWriter
* into the CommandProcessor.
*/
class ResultWriter
{
/** var FormatterManager */
protected $formatterManager;
/** @var callable */
protected $displayErrorFunction;
public function setFormatterManager(FormatterManager $formatterManager)
{
$this->formatterManager = $formatterManager;
return $this;
}
/**
* Return the formatter manager
* @return FormatterManager
*/
public function formatterManager()
{
return $this->formatterManager;
}
public function setDisplayErrorFunction(callable $fn)
{
$this->displayErrorFunction = $fn;
return $this;
}
/**
* Handle the result output and status code calculation.
*/
public function handle(OutputInterface $output, $result, CommandData $commandData, $statusCodeDispatcher = null, $extractDispatcher = null)
{
// A little messy, for backwards compatibility: if the result implements
// ExitCodeInterface, then use that as the exit code. If a status code
// dispatcher returns a non-zero result, then we will never print a
// result.
$status = null;
if ($result instanceof ExitCodeInterface) {
$status = $result->getExitCode();
} elseif (isset($statusCodeDispatcher)) {
$status = $statusCodeDispatcher->determineStatusCode($result);
if (isset($status) && ($status != 0)) {
return $status;
}
}
// If the result is an integer and no separate status code was provided, then use the result as the status and do no output.
if (is_integer($result) && !isset($status)) {
return $result;
}
$status = $this->interpretStatusCode($status);
// Get the structured output, the output stream and the formatter
$structuredOutput = $result;
if (isset($extractDispatcher)) {
$structuredOutput = $extractDispatcher->extractOutput($result);
}
if (($status != 0) && is_string($structuredOutput)) {
$output = $this->chooseOutputStream($output, $status);
return $this->writeErrorMessage($output, $status, $structuredOutput, $result);
}
if ($this->dataCanBeFormatted($structuredOutput) && isset($this->formatterManager)) {
return $this->writeUsingFormatter($output, $structuredOutput, $commandData, $status);
}
return $this->writeCommandOutput($output, $structuredOutput, $status);
}
protected function dataCanBeFormatted($structuredOutput)
{
if (!isset($this->formatterManager)) {
return false;
}
return
is_object($structuredOutput) ||
is_array($structuredOutput);
}
/**
* Determine the formatter that should be used to render
* output.
*
* If the user specified a format via the --format option,
* then always return that. Otherwise, return the default
* format, unless --pipe was specified, in which case
* return the default pipe format, format-pipe.
*
* n.b. --pipe is a handy option introduced in Drush 2
* (or perhaps even Drush 1) that indicates that the command
* should select the output format that is most appropriate
* for use in scripts (e.g. to pipe to another command).
*
* @return string
*/
protected function getFormat(FormatterOptions $options)
{
// In Symfony Console, there is no way for us to differentiate
// between the user specifying '--format=table', and the user
// not specifying --format when the default value is 'table'.
// Therefore, we must make --field always override --format; it
// cannot become the default value for --format.
if ($options->get('field')) {
return 'string';
}
$defaults = [];
if ($options->get('pipe')) {
return $options->get('pipe-format', [], 'tsv');
}
return $options->getFormat($defaults);
}
/**
* Determine whether we should use stdout or stderr.
*/
protected function chooseOutputStream(OutputInterface $output, $status)
{
// If the status code indicates an error, then print the
// result to stderr rather than stdout
if ($status && ($output instanceof ConsoleOutputInterface)) {
return $output->getErrorOutput();
}
return $output;
}
/**
* Call the formatter to output the provided data.
*/
protected function writeUsingFormatter(OutputInterface $output, $structuredOutput, CommandData $commandData, $status = 0)
{
$formatterOptions = $commandData->formatterOptions();
$format = $this->getFormat($formatterOptions);
$this->formatterManager->write(
$output,
$format,
$structuredOutput,
$formatterOptions
);
return $status;
}
/**
* Description
* @param OutputInterface $output
* @param int $status
* @param string $structuredOutput
* @param mixed $originalResult
* @return type
*/
protected function writeErrorMessage($output, $status, $structuredOutput, $originalResult)
{
if (isset($this->displayErrorFunction)) {
call_user_func($this->displayErrorFunction, $output, $structuredOutput, $status, $originalResult);
} else {
$this->writeCommandOutput($output, $structuredOutput);
}
return $status;
}
/**
* If the result object is a string, then print it.
*/
protected function writeCommandOutput(
OutputInterface $output,
$structuredOutput,
$status = 0
) {
// If there is no formatter, we will print strings,
// but can do no more than that.
if (is_string($structuredOutput)) {
$output->writeln($structuredOutput);
}
return $status;
}
/**
* If a status code was set, then return it; otherwise,
* presume success.
*/
protected function interpretStatusCode($status)
{
if (isset($status)) {
return $status;
}
return 0;
}
}
<?php
namespace Consolidation\AnnotatedCommand;
use Symfony\Component\Finder\Finder;
/**
* Do discovery presuming that the namespace of the command will
* contain the last component of the path. This is the convention
* that should be used when searching for command files that are
* bundled with the modules of a framework. The convention used
* is that the namespace for a module in a framework should start with
* the framework name followed by the module name.
*
* For example, if base namespace is "Drupal", then a command file in
* modules/default_content/src/CliTools/ExampleCommands.cpp
* will be in the namespace Drupal\default_content\CliTools.
*
* For global locations, the middle component of the namespace is
* omitted. For example, if the base namespace is "Drupal", then
* a command file in __DRUPAL_ROOT__/CliTools/ExampleCommands.cpp
* will be in the namespace Drupal\CliTools.
*
* To discover namespaced commands in modules:
*
* $commandFiles = $discovery->discoverNamespaced($moduleList, '\Drupal');
*
* To discover global commands:
*
* $commandFiles = $discovery->discover($drupalRoot, '\Drupal');
*
* WARNING:
*
* This class is deprecated. Commandfile discovery is complicated, and does
* not work from within phar files. It is recommended to instead use a static
* list of command classes as shown in https://github.com/g1a/starter/blob/master/example
*
* For a better alternative when implementing a plugin mechanism, see
* https://robo.li/extending/#register-command-files-via-psr-4-autoloading
*/
class CommandFileDiscovery
{
/** @var string[] */
protected $excludeList;
/** @var string[] */
protected $searchLocations;
/** @var string */
protected $searchPattern = '*Commands.php';
/** @var boolean */
protected $includeFilesAtBase = true;
/** @var integer */
protected $searchDepth = 2;
/** @var bool */
protected $followLinks = false;
/** @var string[] */
protected $strippedNamespaces;
public function __construct()
{
$this->excludeList = ['Exclude'];
$this->searchLocations = [
'Command',
'CliTools', // TODO: Maybe remove
];
}
/**
* Specify whether to search for files at the base directory
* ($directoryList parameter to discover and discoverNamespaced
* methods), or only in the directories listed in the search paths.
*
* @param boolean $includeFilesAtBase
*/
public function setIncludeFilesAtBase($includeFilesAtBase)
{
$this->includeFilesAtBase = $includeFilesAtBase;
return $this;
}
/**
* Set the list of excludes to add to the finder, replacing
* whatever was there before.
*
* @param array $excludeList The list of directory names to skip when
* searching for command files.
*/
public function setExcludeList($excludeList)
{
$this->excludeList = $excludeList;
return $this;
}
/**
* Add one more location to the exclude list.
*
* @param string $exclude One directory name to skip when searching
* for command files.
*/
public function addExclude($exclude)
{
$this->excludeList[] = $exclude;
return $this;
}
/**
* Set the search depth. By default, fills immediately in the
* base directory are searched, plus all of the search locations
* to this specified depth. If the search locations is set to
* an empty array, then the base directory is searched to this
* depth.
*/
public function setSearchDepth($searchDepth)
{
$this->searchDepth = $searchDepth;
return $this;
}
/**
* Specify that the discovery object should follow symlinks. By
* default, symlinks are not followed.
*/
public function followLinks($followLinks = true)
{
$this->followLinks = $followLinks;
return $this;
}
/**
* Set the list of search locations to examine in each directory where
* command files may be found. This replaces whatever was there before.
*
* @param array $searchLocations The list of locations to search for command files.
*/
public function setSearchLocations($searchLocations)
{
$this->searchLocations = $searchLocations;
return $this;
}
/**
* Set a particular namespace part to ignore. This is useful in plugin
* mechanisms where the plugin is placed by Composer.
*
* For example, Drush extensions are placed in `./drush/Commands`.
* If the Composer installer path is `"drush/Commands/contrib/{$name}": ["type:drupal-drush"]`,
* then Composer will place the command files in `drush/Commands/contrib`.
* The namespace should not be any different in this instance than if
* the extension were placed in `drush/Commands`, though, so Drush therefore
* calls `ignoreNamespacePart('contrib', 'Commands')`. This causes the
* `contrib` component to be removed from the namespace if it follows
* the namespace `Commands`. If the '$base' parameter is not specified, then
* the ignored portion of the namespace may appear anywhere in the path.
*/
public function ignoreNamespacePart($ignore, $base = '')
{
$replacementPart = '\\';
if (!empty($base)) {
$replacementPart .= $base . '\\';
}
$ignoredPart = $replacementPart . $ignore . '\\';
$this->strippedNamespaces[$ignoredPart] = $replacementPart;
return $this;
}
/**
* Add one more location to the search location list.
*
* @param string $location One more relative path to search
* for command files.
*/
public function addSearchLocation($location)
{
$this->searchLocations[] = $location;
return $this;
}
/**
* Specify the pattern / regex used by the finder to search for
* command files.
*/
public function setSearchPattern($searchPattern)
{
$this->searchPattern = $searchPattern;
return $this;
}
/**
* Given a list of directories, e.g. Drupal modules like:
*
* core/modules/block
* core/modules/dblog
* modules/default_content
*
* Discover command files in any of these locations.
*
* @param string|string[] $directoryList Places to search for commands.
*
* @return array
*/
public function discoverNamespaced($directoryList, $baseNamespace = '')
{
return $this->discover($this->convertToNamespacedList((array)$directoryList), $baseNamespace);
}
/**
* Given a simple list containing paths to directories, where
* the last component of the path should appear in the namespace,
* after the base namespace, this function will return an
* associative array mapping the path's basename (e.g. the module
* name) to the directory path.
*
* Module names must be unique.
*
* @param string[] $directoryList A list of module locations
*
* @return array
*/
public function convertToNamespacedList($directoryList)
{
$namespacedArray = [];
foreach ((array)$directoryList as $directory) {
$namespacedArray[basename($directory)] = $directory;
}
return $namespacedArray;
}
/**
* Search for command files in the specified locations. This is the function that
* should be used for all locations that are NOT modules of a framework.
*
* @param string|string[] $directoryList Places to search for commands.
* @return array
*/
public function discover($directoryList, $baseNamespace = '')
{
$commandFiles = [];
foreach ((array)$directoryList as $key => $directory) {
$itemsNamespace = $this->joinNamespace([$baseNamespace, $key]);
$commandFiles = array_merge(
$commandFiles,
$this->discoverCommandFiles($directory, $itemsNamespace),
$this->discoverCommandFiles("$directory/src", $itemsNamespace)
);
}
return $this->fixNamespaces($commandFiles);
}
/**
* fixNamespaces will alter the namespaces in the commandFiles
* result to remove the Composer placement directory, if any.
*/
protected function fixNamespaces($commandFiles)
{
// Do nothing unless the client told us to remove some namespace components.
if (empty($this->strippedNamespaces)) {
return $commandFiles;
}
// Strip out any part of the namespace the client did not want.
// @see CommandFileDiscovery::ignoreNamespacePart
return array_map(
function ($fqcn) {
return str_replace(
array_keys($this->strippedNamespaces),
array_values($this->strippedNamespaces),
$fqcn
);
},
$commandFiles
);
}
/**
* Search for command files in specific locations within a single directory.
*
* In each location, we will accept only a few places where command files
* can be found. This will reduce the need to search through many unrelated
* files.
*
* The default search locations include:
*
* .
* CliTools
* src/CliTools
*
* The pattern we will look for is any file whose name ends in 'Commands.php'.
* A list of paths to found files will be returned.
*/
protected function discoverCommandFiles($directory, $baseNamespace)
{
$commandFiles = [];
// In the search location itself, we will search for command files
// immediately inside the directory only.
if ($this->includeFilesAtBase) {
$commandFiles = $this->discoverCommandFilesInLocation(
$directory,
$this->getBaseDirectorySearchDepth(),
$baseNamespace
);
}
// In the other search locations,
foreach ($this->searchLocations as $location) {
$itemsNamespace = $this->joinNamespace([$baseNamespace, $location]);
$commandFiles = array_merge(
$commandFiles,
$this->discoverCommandFilesInLocation(
"$directory/$location",
$this->getSearchDepth(),
$itemsNamespace
)
);
}
return $commandFiles;
}
/**
* Return a Finder search depth appropriate for our selected search depth.
*
* @return string
*/
protected function getSearchDepth()
{
return $this->searchDepth <= 0 ? '== 0' : '<= ' . $this->searchDepth;
}
/**
* Return a Finder search depth for the base directory. If the
* searchLocations array has been populated, then we will only search
* for files immediately inside the base directory; no traversal into
* deeper directories will be done, as that would conflict with the
* specification provided by the search locations. If there is no
* search location, then we will search to whatever depth was specified
* by the client.
*
* @return string
*/
protected function getBaseDirectorySearchDepth()
{
if (!empty($this->searchLocations)) {
return '== 0';
}
return $this->getSearchDepth();
}
/**
* Search for command files in just one particular location. Returns
* an associative array mapping from the pathname of the file to the
* classname that it contains. The pathname may be ignored if the search
* location is included in the autoloader.
*
* @param string $directory The location to search
* @param string $depth How deep to search (e.g. '== 0' or '< 2')
* @param string $baseNamespace Namespace to prepend to each classname
*
* @return array
*/
protected function discoverCommandFilesInLocation($directory, $depth, $baseNamespace)
{
if (!is_dir($directory)) {
return [];
}
$finder = $this->createFinder($directory, $depth);
$commands = [];
foreach ($finder as $file) {
$relativePathName = $file->getRelativePathname();
$relativeNamespaceAndClassname = str_replace(
['/', '-', '.php'],
['\\', '_', ''],
$relativePathName
);
$classname = $this->joinNamespace([$baseNamespace, $relativeNamespaceAndClassname]);
$commandFilePath = $this->joinPaths([$directory, $relativePathName]);
$commands[$commandFilePath] = $classname;
}
return $commands;
}
/**
* Create a Finder object for use in searching a particular directory
* location.
*
* @param string $directory The location to search
* @param string $depth The depth limitation
*
* @return Finder
*/
protected function createFinder($directory, $depth)
{
$finder = new Finder();
$finder->files()
->name($this->searchPattern)
->in($directory)
->depth($depth);
foreach ($this->excludeList as $item) {
$finder->exclude($item);
}
if ($this->followLinks) {
$finder->followLinks();
}
return $finder;
}
/**
* Combine the items of the provied array into a backslash-separated
* namespace string. Empty and numeric items are omitted.
*
* @param array $namespaceParts List of components of a namespace
*
* @return string
*/
protected function joinNamespace(array $namespaceParts)
{
return $this->joinParts(
'\\',
$namespaceParts,
function ($item) {
return !is_numeric($item) && !empty($item);
}
);
}
/**
* Combine the items of the provied array into a slash-separated
* pathname. Empty items are omitted.
*
* @param array $pathParts List of components of a path
*
* @return string
*/
protected function joinPaths(array $pathParts)
{
$path = $this->joinParts(
'/',
$pathParts,
function ($item) {
return !empty($item);
}
);
return str_replace(DIRECTORY_SEPARATOR, '/', $path);
}
/**
* Simple wrapper around implode and array_filter.
*
* @param string $delimiter
* @param array $parts
* @param callable $filterFunction
*/
protected function joinParts($delimiter, $parts, $filterFunction)
{
$parts = array_map(
function ($item) use ($delimiter) {
return rtrim($item, $delimiter);
},
$parts
);
return implode(
$delimiter,
array_filter($parts, $filterFunction)
);
}
}
<?php
namespace Consolidation\AnnotatedCommand;
/**
* Command cration listeners can be added to the annotation
* command factory. These will be notified whenever a new
* commandfile is provided to the factory. This is useful for
* initializing new commandfile objects.
*
* @see AnnotatedCommandFactory::addListener()
*/
interface CommandCreationListenerInterface
{
public function notifyCommandFileAdded($command);
}
<?php
namespace Consolidation\AnnotatedCommand;
/**
* Provide an object for the specified interface or class name.
*/
interface ParameterInjector
{
public function get(CommandData $commandData, $interfaceName);
}
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
interface CommandInfoAltererInterface
{
public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance);
}
<?php
namespace Consolidation\AnnotatedCommand\Parser\Internal;
/**
* Hold the tag definition for one tag in a DocBlock.
*
* The tag can be sliced into the following forms:
* - "@tag content"
* - "@tag word description"
* - "@tag $variable description"
* - "@tag word $variable description"
*/
class DocblockTag
{
/** @var string Name of the tag */
protected $tag;
/** @var string|null Contents of the tag. */
protected $content;
const TAG_REGEX = '@(?P<tag>[^\s$]+)[\s]*';
const VARIABLE_REGEX = '\\$(?P<variable>[^\s$]+)[\s]*';
const VARIABLE_OR_WORD_REGEX = '\\$?(?P<variable>[^\s$]+)[\s]*';
const TYPE_REGEX = '(?P<type>[^\s$]+)[\s]*';
const WORD_REGEX = '(?P<word>[^\s$]+)[\s]*';
const DESCRIPTION_REGEX = '(?P<description>.*)';
const IS_TAG_REGEX = '/^[*\s]*@/';
/**
* Check if the provided string begins with a tag
* @param string $subject
* @return bool
*/
public static function isTag($subject)
{
return preg_match(self::IS_TAG_REGEX, $subject);
}
/**
* Use a regular expression to separate the tag from the content.
*
* @param string $subject
* @param string[] &$matches Sets $matches['tag'] and $matches['description']
* @return bool
*/
public static function splitTagAndContent($subject, &$matches)
{
$regex = '/' . self::TAG_REGEX . self::DESCRIPTION_REGEX . '/s';
return preg_match($regex, $subject, $matches);
}
/**
* DockblockTag constructor
*/
public function __construct($tag, $content = null)
{
$this->tag = $tag;
$this->content = $content;
}
/**
* Add more content onto a tag during parsing.
*/
public function appendContent($line)
{
$this->content .= "\n$line";
}
/**
* Return the tag - e.g. "@foo description" returns 'foo'
*
* @return string
*/
public function getTag()
{
return $this->tag;
}
/**
* Return the content portion of the tag - e.g. "@foo bar baz boz" returns
* "bar baz boz"
*
* @return string
*/
public function getContent()
{
return $this->content;
}
/**
* Convert tag back into a string.
*/
public function __toString()
{
return '@' . $this->getTag() . ' ' . $this->getContent();
}
/**
* Determine if tag is one of:
* - "@tag variable description"
* - "@tag $variable description"
* - "@tag type $variable description"
*
* @param string $subject
* @param string[] &$matches Sets $matches['variable'] and
* $matches['description']; might set $matches['type'].
* @return bool
*/
public function hasVariable(&$matches)
{
return
$this->hasTypeVariableAndDescription($matches) ||
$this->hasVariableAndDescription($matches);
}
/**
* Determine if tag is "@tag $variable description"
* @param string $subject
* @param string[] &$matches Sets $matches['variable'] and
* $matches['description']
* @return bool
*/
public function hasVariableAndDescription(&$matches)
{
$regex = '/^\s*' . self::VARIABLE_OR_WORD_REGEX . self::DESCRIPTION_REGEX . '/s';
return preg_match($regex, $this->getContent(), $matches);
}
/**
* Determine if tag is "@tag type $variable description"
*
* @param string $subject
* @param string[] &$matches Sets $matches['variable'],
* $matches['description'] and $matches['type'].
* @return bool
*/
public function hasTypeVariableAndDescription(&$matches)
{
$regex = '/^\s*' . self::TYPE_REGEX . self::VARIABLE_REGEX . self::DESCRIPTION_REGEX . '/s';
return preg_match($regex, $this->getContent(), $matches);
}
/**
* Determine if tag is "@tag word description"
* @param string $subject
* @param string[] &$matches Sets $matches['word'] and
* $matches['description']
* @return bool
*/
public function hasWordAndDescription(&$matches)
{
$regex = '/^\s*' . self::WORD_REGEX . self::DESCRIPTION_REGEX . '/s';
return preg_match($regex, $this->getContent(), $matches);
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser\Internal;
class FullyQualifiedClassCache
{
protected $classCache = [];
protected $namespaceCache = [];
public function qualify($filename, $className)
{
$this->primeCache($filename, $className);
return $this->cached($filename, $className);
}
protected function cached($filename, $className)
{
return isset($this->classCache[$filename][$className]) ? $this->classCache[$filename][$className] : $className;
}
protected function primeCache($filename, $className)
{
// If the cache has already been primed, do no further work
if (isset($this->namespaceCache[$filename])) {
return false;
}
$handle = fopen($filename, "r");
if (!$handle) {
return false;
}
$namespaceName = $this->primeNamespaceCache($filename, $handle);
$this->primeUseCache($filename, $handle);
// If there is no 'use' statement for the className, then
// generate an effective classname from the namespace
if (!isset($this->classCache[$filename][$className])) {
$this->classCache[$filename][$className] = $namespaceName . '\\' . $className;
}
fclose($handle);
}
protected function primeNamespaceCache($filename, $handle)
{
$namespaceName = $this->readNamespace($handle);
if (!$namespaceName) {
return false;
}
$this->namespaceCache[$filename] = $namespaceName;
return $namespaceName;
}
protected function primeUseCache($filename, $handle)
{
$usedClasses = $this->readUseStatements($handle);
if (empty($usedClasses)) {
return false;
}
$this->classCache[$filename] = $usedClasses;
}
protected function readNamespace($handle)
{
$namespaceRegex = '#^\s*namespace\s+#';
$line = $this->readNextRelevantLine($handle);
if (!$line || !preg_match($namespaceRegex, $line)) {
return false;
}
$namespaceName = preg_replace($namespaceRegex, '', $line);
$namespaceName = rtrim($namespaceName, ';');
return $namespaceName;
}
protected function readUseStatements($handle)
{
$useRegex = '#^\s*use\s+#';
$result = [];
while (true) {
$line = $this->readNextRelevantLine($handle);
if (!$line || !preg_match($useRegex, $line)) {
return $result;
}
$usedClass = preg_replace($useRegex, '', $line);
$usedClass = rtrim($usedClass, ';');
$unqualifiedClass = preg_replace('#.*\\\\#', '', $usedClass);
// If this is an aliased class, 'use \Foo\Bar as Baz', then adjust
if (strpos($usedClass, ' as ')) {
$unqualifiedClass = preg_replace('#.*\sas\s+#', '', $usedClass);
$usedClass = preg_replace('#[a-zA-Z0-9]+\s+as\s+#', '', $usedClass);
}
$result[$unqualifiedClass] = $usedClass;
}
}
protected function readNextRelevantLine($handle)
{
while (($line = fgets($handle)) !== false) {
if (preg_match('#^\s*\w#', $line)) {
return trim($line);
}
}
return false;
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser\Internal;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\AnnotatedCommand\Parser\DefaultsWithDescriptions;
/**
* Given a class and method name, parse the annotations in the
* DocBlock comment, and provide accessor methods for all of
* the elements that are needed to create an annotated Command.
*/
class BespokeDocBlockParser
{
protected $fqcnCache;
/**
* @var array
*/
protected $tagProcessors = [
'command' => 'processCommandTag',
'name' => 'processCommandTag',
'arg' => 'processArgumentTag',
'param' => 'processParamTag',
'return' => 'processReturnTag',
'option' => 'processOptionTag',
'default' => 'processDefaultTag',
'aliases' => 'processAliases',
'usage' => 'processUsageTag',
'description' => 'processAlternateDescriptionTag',
'desc' => 'processAlternateDescriptionTag',
];
public function __construct(CommandInfo $commandInfo, \ReflectionMethod $reflection, $fqcnCache = null)
{
$this->commandInfo = $commandInfo;
$this->reflection = $reflection;
$this->fqcnCache = $fqcnCache ?: new FullyQualifiedClassCache();
}
/**
* Parse the docBlock comment for this command, and set the
* fields of this class with the data thereby obtained.
*/
public function parse()
{
$doc = $this->reflection->getDocComment();
$this->parseDocBlock($doc);
}
/**
* Save any tag that we do not explicitly recognize in the
* 'otherAnnotations' map.
*/
protected function processGenericTag($tag)
{
$this->commandInfo->addAnnotation($tag->getTag(), $tag->getContent());
}
/**
* Set the name of the command from a @command or @name annotation.
*/
protected function processCommandTag($tag)
{
if (!$tag->hasWordAndDescription($matches)) {
throw new \Exception('Could not determine command name from tag ' . (string)$tag);
}
$commandName = $matches['word'];
$this->commandInfo->setName($commandName);
// We also store the name in the 'other annotations' so that is is
// possible to determine if the method had a @command annotation.
$this->commandInfo->addAnnotation($tag->getTag(), $commandName);
}
/**
* The @description and @desc annotations may be used in
* place of the synopsis (which we call 'description').
* This is discouraged.
*
* @deprecated
*/
protected function processAlternateDescriptionTag($tag)
{
$this->commandInfo->setDescription($tag->getContent());
}
/**
* Store the data from a @param annotation in our argument descriptions.
*/
protected function processParamTag($tag)
{
if ($tag->hasTypeVariableAndDescription($matches)) {
if ($this->ignoredParamType($matches['type'])) {
return;
}
}
return $this->processArgumentTag($tag);
}
protected function ignoredParamType($paramType)
{
// TODO: We should really only allow a couple of types here,
// e.g. 'string', 'array', 'bool'. Blacklist things we do not
// want for now to avoid breaking commands with weird types.
// Fix in the next major version.
//
// This works:
// return !in_array($paramType, ['string', 'array', 'integer', 'bool']);
return preg_match('#(InputInterface|OutputInterface)$#', $paramType);
}
/**
* Store the data from a @arg annotation in our argument descriptions.
*/
protected function processArgumentTag($tag)
{
if (!$tag->hasVariable($matches)) {
throw new \Exception('Could not determine argument name from tag ' . (string)$tag);
}
if ($matches['variable'] == $this->optionParamName()) {
return;
}
$this->addOptionOrArgumentTag($tag, $this->commandInfo->arguments(), $matches['variable'], $matches['description']);
}
/**
* Store the data from an @option annotation in our option descriptions.
*/
protected function processOptionTag($tag)
{
if (!$tag->hasVariable($matches)) {
throw new \Exception('Could not determine option name from tag ' . (string)$tag);
}
$this->addOptionOrArgumentTag($tag, $this->commandInfo->options(), $matches['variable'], $matches['description']);
}
protected function addOptionOrArgumentTag($tag, DefaultsWithDescriptions $set, $name, $description)
{
$variableName = $this->commandInfo->findMatchingOption($name);
$description = static::removeLineBreaks($description);
list($description, $defaultValue) = $this->splitOutDefault($description);
$set->add($variableName, $description);
if ($defaultValue !== null) {
$set->setDefaultValue($variableName, $defaultValue);
}
}
protected function splitOutDefault($description)
{
if (!preg_match('#(.*)(Default: *)(.*)#', trim($description), $matches)) {
return [$description, null];
}
return [trim($matches[1]), $this->interpretDefaultValue(trim($matches[3]))];
}
/**
* Store the data from a @default annotation in our argument or option store,
* as appropriate.
*/
protected function processDefaultTag($tag)
{
if (!$tag->hasVariable($matches)) {
throw new \Exception('Could not determine parameter name for default value from tag ' . (string)$tag);
}
$variableName = $matches['variable'];
$defaultValue = $this->interpretDefaultValue($matches['description']);
if ($this->commandInfo->arguments()->exists($variableName)) {
$this->commandInfo->arguments()->setDefaultValue($variableName, $defaultValue);
return;
}
$variableName = $this->commandInfo->findMatchingOption($variableName);
if ($this->commandInfo->options()->exists($variableName)) {
$this->commandInfo->options()->setDefaultValue($variableName, $defaultValue);
}
}
/**
* Store the data from a @usage annotation in our example usage list.
*/
protected function processUsageTag($tag)
{
$lines = explode("\n", $tag->getContent());
$usage = trim(array_shift($lines));
$description = static::removeLineBreaks(implode("\n", array_map(function ($line) {
return trim($line);
}, $lines)));
$this->commandInfo->setExampleUsage($usage, $description);
}
/**
* Process the comma-separated list of aliases
*/
protected function processAliases($tag)
{
$this->commandInfo->setAliases((string)$tag->getContent());
}
/**
* Store the data from a @return annotation in our argument descriptions.
*/
protected function processReturnTag($tag)
{
// The return type might be a variable -- '$this'. It will
// usually be a type, like RowsOfFields, or \Namespace\RowsOfFields.
if (!$tag->hasVariableAndDescription($matches)) {
throw new \Exception('Could not determine return type from tag ' . (string)$tag);
}
// Look at namespace and `use` statments to make returnType a fqdn
$returnType = $matches['variable'];
$returnType = $this->findFullyQualifiedClass($returnType);
$this->commandInfo->setReturnType($returnType);
}
protected function findFullyQualifiedClass($className)
{
if (strpos($className, '\\') !== false) {
return $className;
}
return $this->fqcnCache->qualify($this->reflection->getFileName(), $className);
}
private function parseDocBlock($doc)
{
// Remove the leading /** and the trailing */
$doc = preg_replace('#^\s*/\*+\s*#', '', $doc);
$doc = preg_replace('#\s*\*+/\s*#', '', $doc);
// Nothing left? Exit.
if (empty($doc)) {
return;
}
$tagFactory = new TagFactory();
$lines = [];
foreach (explode("\n", $doc) as $row) {
// Remove trailing whitespace and leading space + '*'s
$row = rtrim($row);
$row = preg_replace('#^[ \t]*\**#', '', $row);
if (!$tagFactory->parseLine($row)) {
$lines[] = $row;
}
}
$this->processDescriptionAndHelp($lines);
$this->processAllTags($tagFactory->getTags());
}
protected function processDescriptionAndHelp($lines)
{
// Trim all of the lines individually.
$lines =
array_map(
function ($line) {
return trim($line);
},
$lines
);
// Everything up to the first blank line goes in the description.
$description = array_shift($lines);
while ($this->nextLineIsNotEmpty($lines)) {
$description .= ' ' . array_shift($lines);
}
// Everything else goes in the help.
$help = trim(implode("\n", $lines));
$this->commandInfo->setDescription($description);
$this->commandInfo->setHelp($help);
}
protected function nextLineIsNotEmpty($lines)
{
if (empty($lines)) {
return false;
}
$nextLine = trim($lines[0]);
return !empty($nextLine);
}
protected function processAllTags($tags)
{
// Iterate over all of the tags, and process them as necessary.
foreach ($tags as $tag) {
$processFn = [$this, 'processGenericTag'];
if (array_key_exists($tag->getTag(), $this->tagProcessors)) {
$processFn = [$this, $this->tagProcessors[$tag->getTag()]];
}
$processFn($tag);
}
}
protected function lastParameterName()
{
$params = $this->commandInfo->getParameters();
$param = end($params);
if (!$param) {
return '';
}
return $param->name;
}
/**
* Return the name of the last parameter if it holds the options.
*/
public function optionParamName()
{
// Remember the name of the last parameter, if it holds the options.
// We will use this information to ignore @param annotations for the options.
if (!isset($this->optionParamName)) {
$this->optionParamName = '';
$options = $this->commandInfo->options();
if (!$options->isEmpty()) {
$this->optionParamName = $this->lastParameterName();
}
}
return $this->optionParamName;
}
protected function interpretDefaultValue($defaultValue)
{
$defaults = [
'null' => null,
'true' => true,
'false' => false,
"''" => '',
'[]' => [],
];
foreach ($defaults as $defaultName => $defaultTypedValue) {
if ($defaultValue == $defaultName) {
return $defaultTypedValue;
}
}
return $defaultValue;
}
/**
* Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c',
* convert the data into the last of these forms.
*/
protected static function convertListToCommaSeparated($text)
{
return preg_replace('#[ \t\n\r,]+#', ',', $text);
}
/**
* Take a multiline description and convert it into a single
* long unbroken line.
*/
protected static function removeLineBreaks($text)
{
return trim(preg_replace('#[ \t\n\r]+#', ' ', $text));
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser\Internal;
/**
* Methods to convert to / from a csv string.
*/
class CsvUtils
{
/**
* Ensure that the provided data is a string.
*
* @param string|array $data The data to convert to a string.
* @return string
*/
public static function toString($data)
{
if (is_array($data)) {
return static::csvEscape($data);
}
return $data;
}
/**
* Convert a string to a csv.
*/
public static function csvEscape(array $data, $delimiter = ',')
{
$buffer = fopen('php://temp', 'r+');
fputcsv($buffer, $data, $delimiter);
rewind($buffer);
$csv = fgets($buffer);
fclose($buffer);
return rtrim($csv);
}
/**
* Return a specific named annotation for this command.
*
* @param string|array $data The data to convert to an array.
* @return array
*/
public static function toList($data)
{
if (!is_array($data)) {
return str_getcsv($data);
}
return $data;
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser\Internal;
/**
* Hold some state. Collect tags.
*/
class TagFactory
{
/** @var DocblockTag|null Current tag */
protected $current;
/** @var DocblockTag[] All tag */
protected $tags;
/**
* DocblockTag constructor
*/
public function __construct()
{
$this->current = null;
$this->tags = [];
}
public function parseLine($line)
{
if (DocblockTag::isTag($line)) {
return $this->createTag($line);
}
if (empty($line)) {
return $this->storeCurrentTag();
}
return $this->accumulateContent($line);
}
public function getTags()
{
$this->storeCurrentTag();
return $this->tags;
}
protected function createTag($line)
{
DocblockTag::splitTagAndContent($line, $matches);
$this->storeCurrentTag();
$this->current = new DocblockTag($matches['tag'], $matches['description']);
return true;
}
protected function storeCurrentTag()
{
if (!$this->current) {
return false;
}
$this->tags[] = $this->current;
$this->current = false;
return true;
}
protected function accumulateContent($line)
{
if (!$this->current) {
return false;
}
$this->current->appendContent($line);
return true;
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser\Internal;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
/**
* Create an appropriate CommandDocBlockParser.
*/
class CommandDocBlockParserFactory
{
public static function parse(CommandInfo $commandInfo, \ReflectionMethod $reflection)
{
return static::create($commandInfo, $reflection)->parse();
}
private static function create(CommandInfo $commandInfo, \ReflectionMethod $reflection)
{
return new BespokeDocBlockParser($commandInfo, $reflection);
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser;
use Symfony\Component\Console\Input\InputOption;
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParser;
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParserFactory;
use Consolidation\AnnotatedCommand\AnnotationData;
/**
* Serialize a CommandInfo object
*/
class CommandInfoSerializer
{
public function serialize(CommandInfo $commandInfo)
{
$allAnnotations = $commandInfo->getAnnotations();
$path = $allAnnotations['_path'];
$className = $allAnnotations['_classname'];
// Include the minimum information for command info (including placeholder records)
$info = [
'schema' => CommandInfo::SERIALIZATION_SCHEMA_VERSION,
'class' => $className,
'method_name' => $commandInfo->getMethodName(),
'mtime' => filemtime($path),
'injected_classes' => [],
];
// If this is a valid method / hook, then add more information.
if ($commandInfo->valid()) {
$info += [
'name' => $commandInfo->getName(),
'description' => $commandInfo->getDescription(),
'help' => $commandInfo->getHelp(),
'aliases' => $commandInfo->getAliases(),
'annotations' => $commandInfo->getRawAnnotations()->getArrayCopy(),
'example_usages' => $commandInfo->getExampleUsages(),
'return_type' => $commandInfo->getReturnType(),
];
$info['arguments'] = $this->serializeDefaultsWithDescriptions($commandInfo->arguments());
$info['options'] = $this->serializeDefaultsWithDescriptions($commandInfo->options());
$info['injected_classes'] = $commandInfo->getInjectedClasses();
}
return $info;
}
protected function serializeDefaultsWithDescriptions(DefaultsWithDescriptions $defaults)
{
$result = [];
foreach ($defaults->getValues() as $key => $val) {
$result[$key] = [
'description' => $defaults->getDescription($key),
];
if ($defaults->hasDefault($key)) {
$result[$key]['default'] = $val;
}
}
return $result;
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser;
use Symfony\Component\Console\Input\InputOption;
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParser;
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParserFactory;
use Consolidation\AnnotatedCommand\AnnotationData;
/**
* Deserialize a CommandInfo object
*/
class CommandInfoDeserializer
{
// TODO: in a future version, move CommandInfo::deserialize here
public function deserialize($data)
{
return CommandInfo::deserialize((array)$data);
}
protected static function cachedMethodExists($cache)
{
return method_exists($cache['class'], $cache['method_name']);
}
public static function isValidSerializedData($cache)
{
return
isset($cache['schema']) &&
isset($cache['method_name']) &&
isset($cache['mtime']) &&
($cache['schema'] > 0) &&
($cache['schema'] == CommandInfo::SERIALIZATION_SCHEMA_VERSION) &&
self::cachedMethodExists($cache);
}
public function constructFromCache(CommandInfo $commandInfo, $info_array)
{
$info_array += $this->defaultSerializationData();
$commandInfo
->setName($info_array['name'])
->replaceRawAnnotations($info_array['annotations'])
->setAliases($info_array['aliases'])
->setHelp($info_array['help'])
->setDescription($info_array['description'])
->replaceExampleUsages($info_array['example_usages'])
->setReturnType($info_array['return_type'])
->setInjectedClasses($info_array['injected_classes'])
;
$this->constructDefaultsWithDescriptions($commandInfo->arguments(), (array)$info_array['arguments']);
$this->constructDefaultsWithDescriptions($commandInfo->options(), (array)$info_array['options']);
}
protected function constructDefaultsWithDescriptions(DefaultsWithDescriptions $defaults, $data)
{
foreach ($data as $key => $info) {
$info = (array)$info;
$defaults->add($key, $info['description']);
if (array_key_exists('default', $info)) {
$defaults->setDefaultValue($key, $info['default']);
}
}
}
/**
* Default data. Everything should be provided during serialization;
* this is just as a fallback for unusual circumstances.
* @return array
*/
protected function defaultSerializationData()
{
return [
'name' => '',
'description' => '',
'help' => '',
'aliases' => [],
'annotations' => [],
'example_usages' => [],
'return_type' => [],
'parameters' => [],
'arguments' => [],
'options' => [],
'injected_classes' => [],
'mtime' => 0,
];
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser;
use Symfony\Component\Console\Input\InputOption;
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParser;
use Consolidation\AnnotatedCommand\Parser\Internal\CommandDocBlockParserFactory;
use Consolidation\AnnotatedCommand\AnnotationData;
/**
* Given a class and method name, parse the annotations in the
* DocBlock comment, and provide accessor methods for all of
* the elements that are needed to create a Symfony Console Command.
*
* Note that the name of this class is now somewhat of a misnomer,
* as we now use it to hold annotation data for hooks as well as commands.
* It would probably be better to rename this to MethodInfo at some point.
*/
class CommandInfo
{
/**
* Serialization schema version. Incremented every time the serialization schema changes.
*/
const SERIALIZATION_SCHEMA_VERSION = 4;
/**
* @var \ReflectionMethod
*/
protected $reflection;
/**
* @var boolean
* @var string
*/
protected $docBlockIsParsed = false;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $description = '';
/**
* @var string
*/
protected $help = '';
/**
* @var DefaultsWithDescriptions
*/
protected $options;
/**
* @var DefaultsWithDescriptions
*/
protected $arguments;
/**
* @var array
*/
protected $exampleUsage = [];
/**
* @var AnnotationData
*/
protected $otherAnnotations;
/**
* @var array
*/
protected $aliases = [];
/**
* @var InputOption[]
*/
protected $inputOptions;
/**
* @var string
*/
protected $methodName;
/**
* @var string
*/
protected $returnType;
/**
* @var string[]
*/
protected $injectedClasses = [];
/**
* Create a new CommandInfo class for a particular method of a class.
*
* @param string|mixed $classNameOrInstance The name of a class, or an
* instance of it, or an array of cached data.
* @param string $methodName The name of the method to get info about.
* @param array $cache Cached data
* @deprecated Use CommandInfo::create() or CommandInfo::deserialize()
* instead. In the future, this constructor will be protected.
*/
public function __construct($classNameOrInstance, $methodName, $cache = [])
{
$this->reflection = new \ReflectionMethod($classNameOrInstance, $methodName);
$this->methodName = $methodName;
$this->arguments = new DefaultsWithDescriptions();
$this->options = new DefaultsWithDescriptions();
// If the cache came from a newer version, ignore it and
// regenerate the cached information.
if (!empty($cache) && CommandInfoDeserializer::isValidSerializedData($cache) && !$this->cachedFileIsModified($cache)) {
$deserializer = new CommandInfoDeserializer();
$deserializer->constructFromCache($this, $cache);
$this->docBlockIsParsed = true;
} else {
$this->constructFromClassAndMethod($classNameOrInstance, $methodName);
}
}
public static function create($classNameOrInstance, $methodName)
{
return new self($classNameOrInstance, $methodName);
}
public static function deserialize($cache)
{
$cache = (array)$cache;
return new self($cache['class'], $cache['method_name'], $cache);
}
public function cachedFileIsModified($cache)
{
$path = $this->reflection->getFileName();
return filemtime($path) != $cache['mtime'];
}
protected function constructFromClassAndMethod($classNameOrInstance, $methodName)
{
$this->otherAnnotations = new AnnotationData();
// Set up a default name for the command from the method name.
// This can be overridden via @command or @name annotations.
$this->name = $this->convertName($methodName);
$this->options = new DefaultsWithDescriptions($this->determineOptionsFromParameters(), false);
$this->arguments = $this->determineAgumentClassifications();
}
/**
* Recover the method name provided to the constructor.
*
* @return string
*/
public function getMethodName()
{
return $this->methodName;
}
/**
* Return the primary name for this command.
*
* @return string
*/
public function getName()
{
$this->parseDocBlock();
return $this->name;
}
/**
* Set the primary name for this command.
*
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Return whether or not this method represents a valid command
* or hook.
*/
public function valid()
{
return !empty($this->name);
}
/**
* If higher-level code decides that this CommandInfo is not interesting
* or useful (if it is not a command method or a hook method), then
* we will mark it as invalid to prevent it from being created as a command.
* We still cache a placeholder record for invalid methods, so that we
* do not need to re-parse the method again later simply to determine that
* it is invalid.
*/
public function invalidate()
{
$this->name = '';
}
public function getReturnType()
{
$this->parseDocBlock();
return $this->returnType;
}
public function getInjectedClasses()
{
$this->parseDocBlock();
return $this->injectedClasses;
}
public function setInjectedClasses($injectedClasses)
{
$this->injectedClasses = $injectedClasses;
return $this;
}
public function setReturnType($returnType)
{
$this->returnType = $returnType;
return $this;
}
/**
* Get any annotations included in the docblock comment for the
* implementation method of this command that are not already
* handled by the primary methods of this class.
*
* @return AnnotationData
*/
public function getRawAnnotations()
{
$this->parseDocBlock();
return $this->otherAnnotations;
}
/**
* Replace the annotation data.
*/
public function replaceRawAnnotations($annotationData)
{
$this->otherAnnotations = new AnnotationData((array) $annotationData);
return $this;
}
/**
* Get any annotations included in the docblock comment,
* also including default values such as @command. We add
* in the default @command annotation late, and only in a
* copy of the annotation data because we use the existance
* of a @command to indicate that this CommandInfo is
* a command, and not a hook or anything else.
*
* @return AnnotationData
*/
public function getAnnotations()
{
// Also provide the path to the commandfile that these annotations
// were pulled from and the classname of that file.
$path = $this->reflection->getFileName();
$className = $this->reflection->getDeclaringClass()->getName();
return new AnnotationData(
$this->getRawAnnotations()->getArrayCopy() +
[
'command' => $this->getName(),
'_path' => $path,
'_classname' => $className,
]
);
}
/**
* Return a specific named annotation for this command as a list.
*
* @param string $name The name of the annotation.
* @return array|null
*/
public function getAnnotationList($name)
{
// hasAnnotation parses the docblock
if (!$this->hasAnnotation($name)) {
return null;
}
return $this->otherAnnotations->getList($name);
;
}
/**
* Return a specific named annotation for this command as a string.
*
* @param string $name The name of the annotation.
* @return string|null
*/
public function getAnnotation($name)
{
// hasAnnotation parses the docblock
if (!$this->hasAnnotation($name)) {
return null;
}
return $this->otherAnnotations->get($name);
}
/**
* Check to see if the specified annotation exists for this command.
*
* @param string $annotation The name of the annotation.
* @return boolean
*/
public function hasAnnotation($annotation)
{
$this->parseDocBlock();
return isset($this->otherAnnotations[$annotation]);
}
/**
* Save any tag that we do not explicitly recognize in the
* 'otherAnnotations' map.
*/
public function addAnnotation($name, $content)
{
// Convert to an array and merge if there are multiple
// instances of the same annotation defined.
if (isset($this->otherAnnotations[$name])) {
$content = array_merge((array) $this->otherAnnotations[$name], (array)$content);
}
$this->otherAnnotations[$name] = $content;
}
/**
* Remove an annotation that was previoudly set.
*/
public function removeAnnotation($name)
{
unset($this->otherAnnotations[$name]);
}
/**
* Get the synopsis of the command (~first line).
*
* @return string
*/
public function getDescription()
{
$this->parseDocBlock();
return $this->description;
}
/**
* Set the command description.
*
* @param string $description The description to set.
*/
public function setDescription($description)
{
$this->description = str_replace("\n", ' ', $description);
return $this;
}
/**
* Get the help text of the command (the description)
*/
public function getHelp()
{
$this->parseDocBlock();
return $this->help;
}
/**
* Set the help text for this command.
*
* @param string $help The help text.
*/
public function setHelp($help)
{
$this->help = $help;
return $this;
}
/**
* Return the list of aliases for this command.
* @return string[]
*/
public function getAliases()
{
$this->parseDocBlock();
return $this->aliases;
}
/**
* Set aliases that can be used in place of the command's primary name.
*
* @param string|string[] $aliases
*/
public function setAliases($aliases)
{
if (is_string($aliases)) {
$aliases = explode(',', static::convertListToCommaSeparated($aliases));
}
$this->aliases = array_filter($aliases);
return $this;
}
/**
* Get hidden status for the command.
* @return bool
*/
public function getHidden()
{
$this->parseDocBlock();
return $this->hasAnnotation('hidden');
}
/**
* Set hidden status. List command omits hidden commands.
*
* @param bool $hidden
*/
public function setHidden($hidden)
{
$this->hidden = $hidden;
return $this;
}
/**
* Return the examples for this command. This is @usage instead of
* @example because the later is defined by the phpdoc standard to
* be example method calls.
*
* @return string[]
*/
public function getExampleUsages()
{
$this->parseDocBlock();
return $this->exampleUsage;
}
/**
* Add an example usage for this command.
*
* @param string $usage An example of the command, including the command
* name and all of its example arguments and options.
* @param string $description An explanation of what the example does.
*/
public function setExampleUsage($usage, $description)
{
$this->exampleUsage[$usage] = $description;
return $this;
}
/**
* Overwrite all example usages
*/
public function replaceExampleUsages($usages)
{
$this->exampleUsage = $usages;
return $this;
}
/**
* Return the topics for this command.
*
* @return string[]
*/
public function getTopics()
{
if (!$this->hasAnnotation('topics')) {
return [];
}
$topics = $this->getAnnotation('topics');
return explode(',', trim($topics));
}
/**
* Return the list of refleaction parameters.
*
* @return ReflectionParameter[]
*/
public function getParameters()
{
return $this->reflection->getParameters();
}
/**
* Descriptions of commandline arguements for this command.
*
* @return DefaultsWithDescriptions
*/
public function arguments()
{
return $this->arguments;
}
/**
* Descriptions of commandline options for this command.
*
* @return DefaultsWithDescriptions
*/
public function options()
{
return $this->options;
}
/**
* Get the inputOptions for the options associated with this CommandInfo
* object, e.g. via @option annotations, or from
* $options = ['someoption' => 'defaultvalue'] in the command method
* parameter list.
*
* @return InputOption[]
*/
public function inputOptions()
{
if (!isset($this->inputOptions)) {
$this->inputOptions = $this->createInputOptions();
}
return $this->inputOptions;
}
protected function addImplicitNoOptions()
{
$opts = $this->options()->getValues();
foreach ($opts as $name => $defaultValue) {
if ($defaultValue === true) {
$key = 'no-' . $name;
if (!array_key_exists($key, $opts)) {
$description = "Negate --$name option.";
$this->options()->add($key, $description, false);
}
}
}
}
protected function createInputOptions()
{
$explicitOptions = [];
$this->addImplicitNoOptions();
$opts = $this->options()->getValues();
foreach ($opts as $name => $defaultValue) {
$description = $this->options()->getDescription($name);
$fullName = $name;
$shortcut = '';
if (strpos($name, '|')) {
list($fullName, $shortcut) = explode('|', $name, 2);
}
// Treat the following two cases identically:
// - 'foo' => InputOption::VALUE_OPTIONAL
// - 'foo' => null
// The first form is preferred, but we will convert the value
// to 'null' for storage as the option default value.
if ($defaultValue === InputOption::VALUE_OPTIONAL) {
$defaultValue = null;
}
if ($defaultValue === false) {
$explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_NONE, $description);
} elseif ($defaultValue === InputOption::VALUE_REQUIRED) {
$explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_REQUIRED, $description);
} elseif (is_array($defaultValue)) {
$optionality = count($defaultValue) ? InputOption::VALUE_OPTIONAL : InputOption::VALUE_REQUIRED;
$explicitOptions[$fullName] = new InputOption(
$fullName,
$shortcut,
InputOption::VALUE_IS_ARRAY | $optionality,
$description,
count($defaultValue) ? $defaultValue : null
);
} else {
$explicitOptions[$fullName] = new InputOption($fullName, $shortcut, InputOption::VALUE_OPTIONAL, $description, $defaultValue);
}
}
return $explicitOptions;
}
/**
* An option might have a name such as 'silent|s'. In this
* instance, we will allow the @option or @default tag to
* reference the option only by name (e.g. 'silent' or 's'
* instead of 'silent|s').
*
* @param string $optionName
* @return string
*/
public function findMatchingOption($optionName)
{
// Exit fast if there's an exact match
if ($this->options->exists($optionName)) {
return $optionName;
}
$existingOptionName = $this->findExistingOption($optionName);
if (isset($existingOptionName)) {
return $existingOptionName;
}
return $this->findOptionAmongAlternatives($optionName);
}
/**
* @param string $optionName
* @return string
*/
protected function findOptionAmongAlternatives($optionName)
{
// Check the other direction: if the annotation contains @silent|s
// and the options array has 'silent|s'.
$checkMatching = explode('|', $optionName);
if (count($checkMatching) > 1) {
foreach ($checkMatching as $checkName) {
if ($this->options->exists($checkName)) {
$this->options->rename($checkName, $optionName);
return $optionName;
}
}
}
return $optionName;
}
/**
* @param string $optionName
* @return string|null
*/
protected function findExistingOption($optionName)
{
// Check to see if we can find the option name in an existing option,
// e.g. if the options array has 'silent|s' => false, and the annotation
// is @silent.
foreach ($this->options()->getValues() as $name => $default) {
if (in_array($optionName, explode('|', $name))) {
return $name;
}
}
}
/**
* Examine the parameters of the method for this command, and
* build a list of commandline arguements for them.
*
* @return array
*/
protected function determineAgumentClassifications()
{
$result = new DefaultsWithDescriptions();
$params = $this->reflection->getParameters();
$optionsFromParameters = $this->determineOptionsFromParameters();
if ($this->lastParameterIsOptionsArray()) {
array_pop($params);
}
while (!empty($params) && ($params[0]->getClass() != null)) {
$param = array_shift($params);
$injectedClass = $param->getClass()->getName();
array_unshift($this->injectedClasses, $injectedClass);
}
foreach ($params as $param) {
$this->addParameterToResult($result, $param);
}
return $result;
}
/**
* Examine the provided parameter, and determine whether it
* is a parameter that will be filled in with a positional
* commandline argument.
*/
protected function addParameterToResult($result, $param)
{
// Commandline arguments must be strings, so ignore any
// parameter that is typehinted to any non-primative class.
if ($param->getClass() != null) {
return;
}
$result->add($param->name);
if ($param->isDefaultValueAvailable()) {
$defaultValue = $param->getDefaultValue();
if (!$this->isAssoc($defaultValue)) {
$result->setDefaultValue($param->name, $defaultValue);
}
} elseif ($param->isArray()) {
$result->setDefaultValue($param->name, []);
}
}
/**
* Examine the parameters of the method for this command, and determine
* the disposition of the options from them.
*
* @return array
*/
protected function determineOptionsFromParameters()
{
$params = $this->reflection->getParameters();
if (empty($params)) {
return [];
}
$param = end($params);
if (!$param->isDefaultValueAvailable()) {
return [];
}
if (!$this->isAssoc($param->getDefaultValue())) {
return [];
}
return $param->getDefaultValue();
}
/**
* Determine if the last argument contains $options.
*
* Two forms indicate options:
* - $options = []
* - $options = ['flag' => 'default-value']
*
* Any other form, including `array $foo`, is not options.
*/
protected function lastParameterIsOptionsArray()
{
$params = $this->reflection->getParameters();
if (empty($params)) {
return [];
}
$param = end($params);
if (!$param->isDefaultValueAvailable()) {
return [];
}
return is_array($param->getDefaultValue());
}
/**
* Helper; determine if an array is associative or not. An array
* is not associative if its keys are numeric, and numbered sequentially
* from zero. All other arrays are considered to be associative.
*
* @param array $arr The array
* @return boolean
*/
protected function isAssoc($arr)
{
if (!is_array($arr)) {
return false;
}
return array_keys($arr) !== range(0, count($arr) - 1);
}
/**
* Convert from a method name to the corresponding command name. A
* method 'fooBar' will become 'foo:bar', and 'fooBarBazBoz' will
* become 'foo:bar-baz-boz'.
*
* @param string $camel method name.
* @return string
*/
protected function convertName($camel)
{
$splitter="-";
$camel=preg_replace('/(?!^)[[:upper:]][[:lower:]]/', '$0', preg_replace('/(?!^)[[:upper:]]+/', $splitter.'$0', $camel));
$camel = preg_replace("/$splitter/", ':', $camel, 1);
return strtolower($camel);
}
/**
* Parse the docBlock comment for this command, and set the
* fields of this class with the data thereby obtained.
*/
protected function parseDocBlock()
{
if (!$this->docBlockIsParsed) {
// The parse function will insert data from the provided method
// into this object, using our accessors.
CommandDocBlockParserFactory::parse($this, $this->reflection);
$this->docBlockIsParsed = true;
}
}
/**
* Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c',
* convert the data into the last of these forms.
*/
protected static function convertListToCommaSeparated($text)
{
return preg_replace('#[ \t\n\r,]+#', ',', $text);
}
}
<?php
namespace Consolidation\AnnotatedCommand\Parser;
/**
* An associative array that maps from key to default value;
* each entry can also have a description.
*/
class DefaultsWithDescriptions
{
/**
* @var array Associative array of key : default mappings
*/
protected $values;
/**
* @var array Associative array used like a set to indicate default value
* exists for the key.
*/
protected $hasDefault;
/**
* @var array Associative array of key : description mappings
*/
protected $descriptions;
/**
* @var mixed Default value that the default value of items in
* the collection should take when not specified in the 'add' method.
*/
protected $defaultDefault;
public function __construct($values = [], $defaultDefault = null)
{
$this->values = $values;
$this->hasDefault = array_filter($this->values, function ($value) {
return isset($value);
});
$this->descriptions = [];
$this->defaultDefault = $defaultDefault;
}
/**
* Return just the key : default values mapping
*
* @return array
*/
public function getValues()
{
return $this->values;
}
/**
* Return true if this set of options is empty
*
* @return
*/
public function isEmpty()
{
return empty($this->values);
}
/**
* Check to see whether the speicifed key exists in the collection.
*
* @param string $key
* @return boolean
*/
public function exists($key)
{
return array_key_exists($key, $this->values);
}
/**
* Get the value of one entry.
*
* @param string $key The key of the item.
* @return string
*/
public function get($key)
{
if (array_key_exists($key, $this->values)) {
return $this->values[$key];
}
return $this->defaultDefault;
}
/**
* Get the description of one entry.
*
* @param string $key The key of the item.
* @return string
*/
public function getDescription($key)
{
if (array_key_exists($key, $this->descriptions)) {
return $this->descriptions[$key];
}
return '';
}
/**
* Add another argument to this command.
*
* @param string $key Name of the argument.
* @param string $description Help text for the argument.
* @param mixed $defaultValue The default value for the argument.
*/
public function add($key, $description = '', $defaultValue = null)
{
if (!$this->exists($key) || isset($defaultValue)) {
$this->values[$key] = isset($defaultValue) ? $defaultValue : $this->defaultDefault;
}
unset($this->descriptions[$key]);
if (!empty($description)) {
$this->descriptions[$key] = $description;
}
}
/**
* Change the default value of an entry.
*
* @param string $key
* @param mixed $defaultValue
*/
public function setDefaultValue($key, $defaultValue)
{
$this->values[$key] = $defaultValue;
$this->hasDefault[$key] = true;
return $this;
}
/**
* Check to see if the named argument definitively has a default value.
*
* @param string $key
* @return bool
*/
public function hasDefault($key)
{
return array_key_exists($key, $this->hasDefault);
}
/**
* Remove an entry
*
* @param string $key The entry to remove
*/
public function clear($key)
{
unset($this->values[$key]);
unset($this->descriptions[$key]);
}
/**
* Rename an existing option to something else.
*/
public function rename($oldName, $newName)
{
$this->add($newName, $this->getDescription($oldName), $this->get($oldName));
$this->clear($oldName);
}
}
<?php
namespace Consolidation\AnnotatedCommand;
/**
* Prepare parameter list for execurion. Handle injection of any
* special values (e.g. $input and $output) into the parameter list.
*/
class ParameterInjection implements ParameterInjector
{
public function __construct()
{
$this->register('Symfony\Component\Console\Input\InputInterface', $this);
$this->register('Symfony\Component\Console\Output\OutputInterface', $this);
}
public function register($interfaceName, ParameterInjector $injector)
{
$this->injectors[$interfaceName] = $injector;
}
public function args($commandData)
{
return array_merge(
$commandData->injectedInstances(),
$commandData->getArgsAndOptions()
);
}
public function injectIntoCommandData($commandData, $injectedClasses)
{
foreach ($injectedClasses as $injectedClass) {
$injectedInstance = $this->getInstanceToInject($commandData, $injectedClass);
$commandData->injectInstance($injectedInstance);
}
}
protected function getInstanceToInject(CommandData $commandData, $interfaceName)
{
if (!isset($this->injectors[$interfaceName])) {
return null;
}
return $this->injectors[$interfaceName]->get($commandData, $interfaceName);
}
public function get(CommandData $commandData, $interfaceName)
{
switch ($interfaceName) {
case 'Symfony\Component\Console\Input\InputInterface':
return $commandData->input();
case 'Symfony\Component\Console\Output\OutputInterface':
return $commandData->output();
}
return null;
}
}
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\AnnotatedCommand\Help\HelpDocumentAlter;
use Symfony\Component\Console\Command\Command;
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 Consolidation\AnnotatedCommand\Help\HelpDocumentBuilder;
/**
* AnnotatedCommands are created automatically by the
* AnnotatedCommandFactory. Each command method in a
* command file will produce one AnnotatedCommand. These
* are then added to your Symfony Console Application object;
* nothing else is needed.
*
* Optionally, though, you may extend AnnotatedCommand directly
* to make a single command. The usage pattern is the same
* as for any other Symfony Console command, except that you may
* omit the 'Confiure' method, and instead place your annotations
* on the execute() method.
*
* @package Consolidation\AnnotatedCommand
*/
class AnnotatedCommand extends Command implements HelpDocumentAlter
{
protected $commandCallback;
protected $commandProcessor;
protected $annotationData;
protected $examples = [];
protected $topics = [];
protected $returnType;
protected $injectedClasses = [];
public function __construct($name = null)
{
$commandInfo = false;
// If this is a subclass of AnnotatedCommand, check to see
// if the 'execute' method is annotated. We could do this
// unconditionally; it is a performance optimization to skip
// checking the annotations if $this is an instance of
// AnnotatedCommand. Alternately, we break out a new subclass.
// The command factory instantiates the subclass.
if (get_class($this) != 'Consolidation\AnnotatedCommand\AnnotatedCommand') {
$commandInfo = CommandInfo::create($this, 'execute');
if (!isset($name)) {
$name = $commandInfo->getName();
}
}
parent::__construct($name);
if ($commandInfo && $commandInfo->hasAnnotation('command')) {
$this->setCommandInfo($commandInfo);
$this->setCommandOptions($commandInfo);
}
}
public function setCommandCallback($commandCallback)
{
$this->commandCallback = $commandCallback;
return $this;
}
public function setCommandProcessor($commandProcessor)
{
$this->commandProcessor = $commandProcessor;
return $this;
}
public function commandProcessor()
{
// If someone is using an AnnotatedCommand, and is NOT getting
// it from an AnnotatedCommandFactory OR not correctly injecting
// a command processor via setCommandProcessor() (ideally via the
// DI container), then we'll just give each annotated command its
// own command processor. This is not ideal; preferably, there would
// only be one instance of the command processor in the application.
if (!isset($this->commandProcessor)) {
$this->commandProcessor = new CommandProcessor(new HookManager());
}
return $this->commandProcessor;
}
public function getReturnType()
{
return $this->returnType;
}
public function setReturnType($returnType)
{
$this->returnType = $returnType;
return $this;
}
public function getAnnotationData()
{
return $this->annotationData;
}
public function setAnnotationData($annotationData)
{
$this->annotationData = $annotationData;
return $this;
}
public function getTopics()
{
return $this->topics;
}
public function setTopics($topics)
{
$this->topics = $topics;
return $this;
}
public function setCommandInfo($commandInfo)
{
$this->setDescription($commandInfo->getDescription());
$this->setHelp($commandInfo->getHelp());
$this->setAliases($commandInfo->getAliases());
$this->setAnnotationData($commandInfo->getAnnotations());
$this->setTopics($commandInfo->getTopics());
foreach ($commandInfo->getExampleUsages() as $usage => $description) {
$this->addUsageOrExample($usage, $description);
}
$this->setCommandArguments($commandInfo);
$this->setReturnType($commandInfo->getReturnType());
// Hidden commands available since Symfony 3.2
// http://symfony.com/doc/current/console/hide_commands.html
if (method_exists($this, 'setHidden')) {
$this->setHidden($commandInfo->getHidden());
}
return $this;
}
public function getExampleUsages()
{
return $this->examples;
}
protected function addUsageOrExample($usage, $description)
{
$this->addUsage($usage);
if (!empty($description)) {
$this->examples[$usage] = $description;
}
}
public function helpAlter(\DomDocument $originalDom)
{
return HelpDocumentBuilder::alter($originalDom, $this);
}
protected function setCommandArguments($commandInfo)
{
$this->injectedClasses = $commandInfo->getInjectedClasses();
$this->setCommandArgumentsFromParameters($commandInfo);
return $this;
}
protected function setCommandArgumentsFromParameters($commandInfo)
{
$args = $commandInfo->arguments()->getValues();
foreach ($args as $name => $defaultValue) {
$description = $commandInfo->arguments()->getDescription($name);
$hasDefault = $commandInfo->arguments()->hasDefault($name);
$parameterMode = $this->getCommandArgumentMode($hasDefault, $defaultValue);
$this->addArgument($name, $parameterMode, $description, $defaultValue);
}
return $this;
}
protected function getCommandArgumentMode($hasDefault, $defaultValue)
{
if (!$hasDefault) {
return InputArgument::REQUIRED;
}
if (is_array($defaultValue)) {
return InputArgument::IS_ARRAY;
}
return InputArgument::OPTIONAL;
}
public function setCommandOptions($commandInfo, $automaticOptions = [])
{
$inputOptions = $commandInfo->inputOptions();
$this->addOptions($inputOptions + $automaticOptions, $automaticOptions);
return $this;
}
public function addOptions($inputOptions, $automaticOptions = [])
{
foreach ($inputOptions as $name => $inputOption) {
$description = $inputOption->getDescription();
if (empty($description) && isset($automaticOptions[$name])) {
$description = $automaticOptions[$name]->getDescription();
$inputOption = static::inputOptionSetDescription($inputOption, $description);
}
$this->getDefinition()->addOption($inputOption);
}
}
protected static function inputOptionSetDescription($inputOption, $description)
{
// Recover the 'mode' value, because Symfony is stubborn
$mode = 0;
if ($inputOption->isValueRequired()) {
$mode |= InputOption::VALUE_REQUIRED;
}
if ($inputOption->isValueOptional()) {
$mode |= InputOption::VALUE_OPTIONAL;
}
if ($inputOption->isArray()) {
$mode |= InputOption::VALUE_IS_ARRAY;
}
if (!$mode) {
$mode = InputOption::VALUE_NONE;
}
$inputOption = new InputOption(
$inputOption->getName(),
$inputOption->getShortcut(),
$mode,
$description,
$inputOption->getDefault()
);
return $inputOption;
}
/**
* Returns all of the hook names that may be called for this command.
*
* @return array
*/
public function getNames()
{
return HookManager::getNames($this, $this->commandCallback);
}
/**
* Add any options to this command that are defined by hook implementations
*/
public function optionsHook()
{
$this->commandProcessor()->optionsHook(
$this,
$this->getNames(),
$this->annotationData
);
}
public function optionsHookForHookAnnotations($commandInfoList)
{
foreach ($commandInfoList as $commandInfo) {
$inputOptions = $commandInfo->inputOptions();
$this->addOptions($inputOptions);
foreach ($commandInfo->getExampleUsages() as $usage => $description) {
if (!in_array($usage, $this->getUsages())) {
$this->addUsageOrExample($usage, $description);
}
}
}
}
/**
* {@inheritdoc}
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
$this->commandProcessor()->interact(
$input,
$output,
$this->getNames(),
$this->annotationData
);
}
protected function initialize(InputInterface $input, OutputInterface $output)
{
// Allow the hook manager a chance to provide configuration values,
// if there are any registered hooks to do that.
$this->commandProcessor()->initializeHook($input, $this->getNames(), $this->annotationData);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
// Validate, run, process, alter, handle results.
return $this->commandProcessor()->process(
$output,
$this->getNames(),
$this->commandCallback,
$this->createCommandData($input, $output)
);
}
/**
* This function is available for use by a class that may
* wish to extend this class rather than use annotations to
* define commands. Using this technique does allow for the
* use of annotations to define hooks.
*/
public function processResults(InputInterface $input, OutputInterface $output, $results)
{
$commandData = $this->createCommandData($input, $output);
$commandProcessor = $this->commandProcessor();
$names = $this->getNames();
$results = $commandProcessor->processResults(
$names,
$results,
$commandData
);
return $commandProcessor->handleResults(
$output,
$names,
$results,
$commandData
);
}
protected function createCommandData(InputInterface $input, OutputInterface $output)
{
$commandData = new CommandData(
$this->annotationData,
$input,
$output
);
// Fetch any classes (e.g. InputInterface / OutputInterface) that
// this command's callback wants passed as a parameter and inject
// it into the command data.
$this->commandProcessor()->injectIntoCommandData($commandData, $this->injectedClasses);
// Allow the commandData to cache the list of options with
// special default values ('null' and 'true'), as these will
// need special handling. @see CommandData::options().
$commandData->cacheSpecialDefaults($this->getDefinition());
return $commandData;
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
use Consolidation\AnnotatedCommand\CommandData;
/**
* A result processor takes a result object, processes it, and
* returns another result object. For example, if a result object
* represents a 'task', then a task-runner hook could run the
* task and return the result from that execution.
*
* @see HookManager::addResultProcessor()
*/
interface ProcessResultInterface
{
/**
* After a command has executed, if the result is something
* that needs to be processed, e.g. a collection of tasks to
* run, then execute it and return the new result.
*
* @param mixed $result Result to (potentially) be processed
* @param CommandData $commandData Reference to commandline arguments and options
*
* @return mixed $result
*/
public function process($result, CommandData $commandData);
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
use Consolidation\AnnotatedCommand\AnnotationData;
use Symfony\Component\Console\Command\Command;
/**
* Add options to a command.
*
* @see HookManager::addOptionHook()
* @see AnnotatedCommandFactory::addListener()
*/
interface OptionHookInterface
{
public function getOptions(Command $command, AnnotationData $annotationData);
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
/**
* Extract Output hooks are used to select the particular
* data elements of the result that should be printed as
* the command output -- perhaps after being formatted.
*
* @see HookManager::addOutputExtractor()
*/
interface ExtractOutputInterface
{
public function extractOutput($result);
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
/**
* A StatusDeterminer maps from a result to a status exit code.
*
* @deprecated. Instead of using a Status Determiner hook, commands
* should simply return their exit code and result data separately
* using a CommandResult object.
*
* @see HookManager::addStatusDeterminer()
*/
interface StatusDeterminerInterface
{
/**
* Convert a result object into a status code, if
* possible. Return null if the result object is unknown.
*
* @return null|integer
*/
public function determineStatusCode($result);
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
/**
* Alter the result of a command after it has been processed.
* An alter result interface isa process result interface.
*
* @see HookManager::addAlterResult()
*/
interface AlterResultInterface extends ProcessResultInterface
{
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Consolidation\AnnotatedCommand\ExitCodeInterface;
use Consolidation\AnnotatedCommand\OutputDataInterface;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\CommandError;
use Consolidation\AnnotatedCommand\Hooks\Dispatchers\CommandEventHookDispatcher;
/**
* Manage named callback hooks
*/
class HookManager implements EventSubscriberInterface
{
protected $hooks = [];
/** var CommandInfo[] */
protected $hookOptions = [];
const REPLACE_COMMAND_HOOK = 'replace-command';
const PRE_COMMAND_EVENT = 'pre-command-event';
const COMMAND_EVENT = 'command-event';
const POST_COMMAND_EVENT = 'post-command-event';
const PRE_OPTION_HOOK = 'pre-option';
const OPTION_HOOK = 'option';
const POST_OPTION_HOOK = 'post-option';
const PRE_INITIALIZE = 'pre-init';
const INITIALIZE = 'init';
const POST_INITIALIZE = 'post-init';
const PRE_INTERACT = 'pre-interact';
const INTERACT = 'interact';
const POST_INTERACT = 'post-interact';
const PRE_ARGUMENT_VALIDATOR = 'pre-validate';
const ARGUMENT_VALIDATOR = 'validate';
const POST_ARGUMENT_VALIDATOR = 'post-validate';
const PRE_COMMAND_HOOK = 'pre-command';
const COMMAND_HOOK = 'command';
const POST_COMMAND_HOOK = 'post-command';
const PRE_PROCESS_RESULT = 'pre-process';
const PROCESS_RESULT = 'process';
const POST_PROCESS_RESULT = 'post-process';
const PRE_ALTER_RESULT = 'pre-alter';
const ALTER_RESULT = 'alter';
const POST_ALTER_RESULT = 'post-alter';
const STATUS_DETERMINER = 'status';
const EXTRACT_OUTPUT = 'extract';
const ON_EVENT = 'on-event';
public function __construct()
{
}
public function getAllHooks()
{
return $this->hooks;
}
/**
* Add a hook
*
* @param mixed $callback The callback function to call
* @param string $hook The name of the hook to add
* @param string $name The name of the command to hook
* ('*' for all)
*/
public function add(callable $callback, $hook, $name = '*')
{
if (empty($name)) {
$name = static::getClassNameFromCallback($callback);
}
$this->hooks[$name][$hook][] = $callback;
return $this;
}
public function recordHookOptions($commandInfo, $name)
{
$this->hookOptions[$name][] = $commandInfo;
return $this;
}
public static function getNames($command, $callback)
{
return array_filter(
array_merge(
static::getNamesUsingCommands($command),
[static::getClassNameFromCallback($callback)]
)
);
}
protected static function getNamesUsingCommands($command)
{
return array_merge(
[$command->getName()],
$command->getAliases()
);
}
/**
* If a command hook does not specify any particular command
* name that it should be attached to, then it will be applied
* to every command that is defined in the same class as the hook.
* This is controlled by using the namespace + class name of
* the implementing class of the callback hook.
*/
protected static function getClassNameFromCallback($callback)
{
if (!is_array($callback)) {
return '';
}
$reflectionClass = new \ReflectionClass($callback[0]);
return $reflectionClass->getName();
}
/**
* Add a replace command hook
*
* @param type ReplaceCommandHookInterface $provider
* @param type string $command_name The name of the command to replace
*/
public function addReplaceCommandHook(ReplaceCommandHookInterface $replaceCommandHook, $name)
{
$this->hooks[$name][self::REPLACE_COMMAND_HOOK][] = $replaceCommandHook;
return $this;
}
public function addPreCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
{
$this->hooks[$name][self::PRE_COMMAND_EVENT][] = $eventDispatcher;
return $this;
}
public function addCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
{
$this->hooks[$name][self::COMMAND_EVENT][] = $eventDispatcher;
return $this;
}
public function addPostCommandEventDispatcher(EventDispatcherInterface $eventDispatcher, $name = '*')
{
$this->hooks[$name][self::POST_COMMAND_EVENT][] = $eventDispatcher;
return $this;
}
public function addCommandEvent(EventSubscriberInterface $eventSubscriber)
{
// Wrap the event subscriber in a dispatcher and add it
$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber($eventSubscriber);
return $this->addCommandEventDispatcher($dispatcher);
}
/**
* Add an configuration provider hook
*
* @param type InitializeHookInterface $provider
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addInitializeHook(InitializeHookInterface $initializeHook, $name = '*')
{
$this->hooks[$name][self::INITIALIZE][] = $initializeHook;
return $this;
}
/**
* Add an option hook
*
* @param type ValidatorInterface $validator
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addOptionHook(OptionHookInterface $interactor, $name = '*')
{
$this->hooks[$name][self::INTERACT][] = $interactor;
return $this;
}
/**
* Add an interact hook
*
* @param type ValidatorInterface $validator
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addInteractor(InteractorInterface $interactor, $name = '*')
{
$this->hooks[$name][self::INTERACT][] = $interactor;
return $this;
}
/**
* Add a pre-validator hook
*
* @param type ValidatorInterface $validator
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addPreValidator(ValidatorInterface $validator, $name = '*')
{
$this->hooks[$name][self::PRE_ARGUMENT_VALIDATOR][] = $validator;
return $this;
}
/**
* Add a validator hook
*
* @param type ValidatorInterface $validator
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addValidator(ValidatorInterface $validator, $name = '*')
{
$this->hooks[$name][self::ARGUMENT_VALIDATOR][] = $validator;
return $this;
}
/**
* Add a pre-command hook. This is the same as a validator hook, except
* that it will run after all of the post-validator hooks.
*
* @param type ValidatorInterface $preCommand
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addPreCommandHook(ValidatorInterface $preCommand, $name = '*')
{
$this->hooks[$name][self::PRE_COMMAND_HOOK][] = $preCommand;
return $this;
}
/**
* Add a post-command hook. This is the same as a pre-process hook,
* except that it will run before the first pre-process hook.
*
* @param type ProcessResultInterface $postCommand
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addPostCommandHook(ProcessResultInterface $postCommand, $name = '*')
{
$this->hooks[$name][self::POST_COMMAND_HOOK][] = $postCommand;
return $this;
}
/**
* Add a result processor.
*
* @param type ProcessResultInterface $resultProcessor
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addResultProcessor(ProcessResultInterface $resultProcessor, $name = '*')
{
$this->hooks[$name][self::PROCESS_RESULT][] = $resultProcessor;
return $this;
}
/**
* Add a result alterer. After a result is processed
* by a result processor, an alter hook may be used
* to convert the result from one form to another.
*
* @param type AlterResultInterface $resultAlterer
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addAlterResult(AlterResultInterface $resultAlterer, $name = '*')
{
$this->hooks[$name][self::ALTER_RESULT][] = $resultAlterer;
return $this;
}
/**
* Add a status determiner. Usually, a command should return
* an integer on error, or a result object on success (which
* implies a status code of zero). If a result contains the
* status code in some other field, then a status determiner
* can be used to call the appropriate accessor method to
* determine the status code. This is usually not necessary,
* though; a command that fails may return a CommandError
* object, which contains a status code and a result message
* to display.
* @see CommandError::getExitCode()
*
* @param type StatusDeterminerInterface $statusDeterminer
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addStatusDeterminer(StatusDeterminerInterface $statusDeterminer, $name = '*')
{
$this->hooks[$name][self::STATUS_DETERMINER][] = $statusDeterminer;
return $this;
}
/**
* Add an output extractor. If a command returns an object
* object, by default it is passed directly to the output
* formatter (if in use) for rendering. If the result object
* contains more information than just the data to render, though,
* then an output extractor can be used to call the appopriate
* accessor method of the result object to get the data to
* rendered. This is usually not necessary, though; it is preferable
* to have complex result objects implement the OutputDataInterface.
* @see OutputDataInterface::getOutputData()
*
* @param type ExtractOutputInterface $outputExtractor
* @param type $name The name of the command to hook
* ('*' for all)
*/
public function addOutputExtractor(ExtractOutputInterface $outputExtractor, $name = '*')
{
$this->hooks[$name][self::EXTRACT_OUTPUT][] = $outputExtractor;
return $this;
}
public function getHookOptionsForCommand($command)
{
$names = $this->addWildcardHooksToNames($command->getNames(), $command->getAnnotationData());
return $this->getHookOptions($names);
}
/**
* @return CommandInfo[]
*/
public function getHookOptions($names)
{
$result = [];
foreach ($names as $name) {
if (isset($this->hookOptions[$name])) {
$result = array_merge($result, $this->hookOptions[$name]);
}
}
return $result;
}
/**
* Get a set of hooks with the provided name(s). Include the
* pre- and post- hooks, and also include the global hooks ('*')
* in addition to the named hooks provided.
*
* @param string|array $names The name of the function being hooked.
* @param string[] $hooks A list of hooks (e.g. [HookManager::ALTER_RESULT])
*
* @return callable[]
*/
public function getHooks($names, $hooks, $annotationData = null)
{
return $this->get($this->addWildcardHooksToNames($names, $annotationData), $hooks);
}
protected function addWildcardHooksToNames($names, $annotationData = null)
{
$names = array_merge(
(array)$names,
($annotationData == null) ? [] : array_map(function ($item) {
return "@$item";
}, $annotationData->keys())
);
$names[] = '*';
return array_unique($names);
}
/**
* Get a set of hooks with the provided name(s).
*
* @param string|array $names The name of the function being hooked.
* @param string[] $hooks The list of hook names (e.g. [HookManager::ALTER_RESULT])
*
* @return callable[]
*/
public function get($names, $hooks)
{
$result = [];
foreach ((array)$hooks as $hook) {
foreach ((array)$names as $name) {
$result = array_merge($result, $this->getHook($name, $hook));
}
}
return $result;
}
/**
* Get a single named hook.
*
* @param string $name The name of the hooked method
* @param string $hook The specific hook name (e.g. alter)
*
* @return callable[]
*/
public function getHook($name, $hook)
{
if (isset($this->hooks[$name][$hook])) {
return $this->hooks[$name][$hook];
}
return [];
}
/**
* Call the command event hooks.
*
* TODO: This should be moved to CommandEventHookDispatcher, which
* should become the class that implements EventSubscriberInterface.
* This change would break all clients, though, so postpone until next
* major release.
*
* @param ConsoleCommandEvent $event
*/
public function callCommandEventHooks(ConsoleCommandEvent $event)
{
/* @var Command $command */
$command = $event->getCommand();
$dispatcher = new CommandEventHookDispatcher($this, [$command->getName()]);
$dispatcher->callCommandEventHooks($event);
}
/**
* @{@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [ConsoleEvents::COMMAND => 'callCommandEventHooks'];
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Call hooks
*/
class CommandEventHookDispatcher extends HookDispatcher
{
/**
* @param ConsoleCommandEvent $event
*/
public function callCommandEventHooks(ConsoleCommandEvent $event)
{
$hooks = [
HookManager::PRE_COMMAND_EVENT,
HookManager::COMMAND_EVENT,
HookManager::POST_COMMAND_EVENT
];
$commandEventHooks = $this->getHooks($hooks);
foreach ($commandEventHooks as $commandEvent) {
if ($commandEvent instanceof EventDispatcherInterface) {
$commandEvent->dispatch(ConsoleEvents::COMMAND, $event);
}
if (is_callable($commandEvent)) {
$commandEvent($event);
}
}
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
use Consolidation\AnnotatedCommand\Hooks\ExtractOutputInterface;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\OutputDataInterface;
/**
* Call hooks
*/
class ExtracterHookDispatcher extends HookDispatcher implements ExtractOutputInterface
{
/**
* Convert the result object to printable output in
* structured form.
*/
public function extractOutput($result)
{
if ($result instanceof OutputDataInterface) {
return $result->getOutputData();
}
$hooks = [
HookManager::EXTRACT_OUTPUT,
];
$extractors = $this->getHooks($hooks);
foreach ($extractors as $extractor) {
$structuredOutput = $this->callExtractor($extractor, $result);
if (isset($structuredOutput)) {
return $structuredOutput;
}
}
return $result;
}
protected function callExtractor($extractor, $result)
{
if ($extractor instanceof ExtractOutputInterface) {
return $extractor->extractOutput($result);
}
if (is_callable($extractor)) {
return $extractor($result);
}
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\AnnotationData;
/**
* Call hooks
*/
class HookDispatcher
{
/** var HookManager */
protected $hookManager;
protected $names;
public function __construct(HookManager $hookManager, $names)
{
$this->hookManager = $hookManager;
$this->names = $names;
}
public function getHooks($hooks, $annotationData = null)
{
return $this->hookManager->getHooks($this->names, $hooks, $annotationData);
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\Hooks\InitializeHookInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
/**
* Call hooks
*/
class InitializeHookDispatcher extends HookDispatcher implements InitializeHookInterface
{
public function initialize(
InputInterface $input,
AnnotationData $annotationData
) {
$hooks = [
HookManager::PRE_INITIALIZE,
HookManager::INITIALIZE,
HookManager::POST_INITIALIZE
];
$providers = $this->getHooks($hooks, $annotationData);
foreach ($providers as $provider) {
$this->callInitializeHook($provider, $input, $annotationData);
}
}
protected function callInitializeHook($provider, $input, AnnotationData $annotationData)
{
if ($provider instanceof InitializeHookInterface) {
return $provider->initialize($input, $annotationData);
}
if (is_callable($provider)) {
return $provider($input, $annotationData);
}
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
use Symfony\Component\Console\Command\Command;
use Consolidation\AnnotatedCommand\AnnotatedCommand;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Hooks\OptionHookInterface;
/**
* Call hooks
*/
class OptionsHookDispatcher extends HookDispatcher implements OptionHookInterface
{
public function getOptions(
Command $command,
AnnotationData $annotationData
) {
$hooks = [
HookManager::PRE_OPTION_HOOK,
HookManager::OPTION_HOOK,
HookManager::POST_OPTION_HOOK
];
$optionHooks = $this->getHooks($hooks, $annotationData);
foreach ($optionHooks as $optionHook) {
$this->callOptionHook($optionHook, $command, $annotationData);
}
$commandInfoList = $this->hookManager->getHookOptionsForCommand($command);
if ($command instanceof AnnotatedCommand) {
$command->optionsHookForHookAnnotations($commandInfoList);
}
}
protected function callOptionHook($optionHook, $command, AnnotationData $annotationData)
{
if ($optionHook instanceof OptionHookInterface) {
return $optionHook->getOptions($command, $annotationData);
}
if (is_callable($optionHook)) {
return $optionHook($command, $annotationData);
}
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\CommandError;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Hooks\ValidatorInterface;
/**
* Call hooks
*/
class ValidateHookDispatcher extends HookDispatcher implements ValidatorInterface
{
public function validate(CommandData $commandData)
{
$hooks = [
HookManager::PRE_ARGUMENT_VALIDATOR,
HookManager::ARGUMENT_VALIDATOR,
HookManager::POST_ARGUMENT_VALIDATOR,
HookManager::PRE_COMMAND_HOOK,
HookManager::COMMAND_HOOK,
];
$validators = $this->getHooks($hooks, $commandData->annotationData());
foreach ($validators as $validator) {
$validated = $this->callValidator($validator, $commandData);
if ($validated === false) {
return new CommandError();
}
if (is_object($validated)) {
return $validated;
}
}
}
protected function callValidator($validator, CommandData $commandData)
{
if ($validator instanceof ValidatorInterface) {
return $validator->validate($commandData);
}
if (is_callable($validator)) {
return $validator($commandData);
}
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Hooks\InteractorInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Call hooks
*/
class InteractHookDispatcher extends HookDispatcher
{
public function interact(
InputInterface $input,
OutputInterface $output,
AnnotationData $annotationData
) {
$hooks = [
HookManager::PRE_INTERACT,
HookManager::INTERACT,
HookManager::POST_INTERACT
];
$interactors = $this->getHooks($hooks, $annotationData);
foreach ($interactors as $interactor) {
$this->callInteractor($interactor, $input, $output, $annotationData);
}
}
protected function callInteractor($interactor, $input, $output, AnnotationData $annotationData)
{
if ($interactor instanceof InteractorInterface) {
return $interactor->interact($input, $output, $annotationData);
}
if (is_callable($interactor)) {
return $interactor($input, $output, $annotationData);
}
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Hooks\ProcessResultInterface;
/**
* Call hooks
*/
class ProcessResultHookDispatcher extends HookDispatcher implements ProcessResultInterface
{
/**
* Process result and decide what to do with it.
* Allow client to add transformation / interpretation
* callbacks.
*/
public function process($result, CommandData $commandData)
{
$hooks = [
HookManager::PRE_PROCESS_RESULT,
HookManager::PROCESS_RESULT,
HookManager::POST_PROCESS_RESULT,
HookManager::PRE_ALTER_RESULT,
HookManager::ALTER_RESULT,
HookManager::POST_ALTER_RESULT,
HookManager::POST_COMMAND_HOOK,
];
$processors = $this->getHooks($hooks, $commandData->annotationData());
foreach ($processors as $processor) {
$result = $this->callProcessor($processor, $result, $commandData);
}
return $result;
}
protected function callProcessor($processor, $result, CommandData $commandData)
{
$processed = null;
if ($processor instanceof ProcessResultInterface) {
$processed = $processor->process($result, $commandData);
}
if (is_callable($processor)) {
$processed = $processor($result, $commandData);
}
if (isset($processed)) {
return $processed;
}
return $result;
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
/**
* Call hooks.
*/
class ReplaceCommandHookDispatcher extends HookDispatcher implements LoggerAwareInterface
{
use LoggerAwareTrait;
/**
* @return int
*/
public function hasReplaceCommandHook()
{
return (bool) count($this->getReplaceCommandHooks());
}
/**
* @return \callable[]
*/
public function getReplaceCommandHooks()
{
$hooks = [
HookManager::REPLACE_COMMAND_HOOK,
];
$replaceCommandHooks = $this->getHooks($hooks);
return $replaceCommandHooks;
}
/**
* @param \Consolidation\AnnotatedCommand\CommandData $commandData
*
* @return callable
*/
public function getReplacementCommand(CommandData $commandData)
{
$replaceCommandHooks = $this->getReplaceCommandHooks();
// We only take the first hook implementation of "replace-command" as the replacement. Commands shouldn't have
// more than one replacement.
$replacementCommand = reset($replaceCommandHooks);
if ($this->logger && count($replaceCommandHooks) > 1) {
$command_name = $commandData->annotationData()->get('command', 'unknown');
$message = "Multiple implementations of the \"replace - command\" hook exist for the \"$command_name\" command.\n";
foreach ($replaceCommandHooks as $replaceCommandHook) {
$class = get_class($replaceCommandHook[0]);
$method = $replaceCommandHook[1];
$hook_name = "$class->$method";
$message .= " - $hook_name\n";
}
$this->logger->warning($message);
}
return $replacementCommand;
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks\Dispatchers;
use Consolidation\AnnotatedCommand\ExitCodeInterface;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Hooks\StatusDeterminerInterface;
/**
* Call hooks
*/
class StatusDeterminerHookDispatcher extends HookDispatcher implements StatusDeterminerInterface
{
/**
* Call all status determiners, and see if any of them
* know how to convert to a status code.
*/
public function determineStatusCode($result)
{
// If the result (post-processing) is an object that
// implements ExitCodeInterface, then we will ask it
// to give us the status code.
if ($result instanceof ExitCodeInterface) {
return $result->getExitCode();
}
$hooks = [
HookManager::STATUS_DETERMINER,
];
// If the result does not implement ExitCodeInterface,
// then we'll see if there is a determiner that can
// extract a status code from the result.
$determiners = $this->getHooks($hooks);
foreach ($determiners as $determiner) {
$status = $this->callDeterminer($determiner, $result);
if (isset($status)) {
return $status;
}
}
}
protected function callDeterminer($determiner, $result)
{
if ($determiner instanceof StatusDeterminerInterface) {
return $determiner->determineStatusCode($result);
}
if (is_callable($determiner)) {
return $determiner($result);
}
}
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
use Consolidation\AnnotatedCommand\CommandData;
/**
* Validate the arguments for the current command.
*
* @see HookManager::addValidator()
*/
interface ValidatorInterface
{
public function validate(CommandData $commandData);
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
use Consolidation\AnnotatedCommand\AnnotationData;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Interactively supply values for missing required arguments for
* the current command. Note that this hook is not called if
* the --no-interaction flag is set.
*
* @see HookManager::addInteractor()
*/
interface InteractorInterface
{
public function interact(InputInterface $input, OutputInterface $output, AnnotationData $annotationData);
}
<?php
namespace Consolidation\AnnotatedCommand\Hooks;
use Consolidation\AnnotatedCommand\AnnotationData;
use Symfony\Component\Console\Input\InputInterface;
/**
* Non-interactively (e.g. via configuration files) apply configuration values to the Input object.
*
* @see HookManager::addInitializeHook()
*/
interface InitializeHookInterface
{
public function initialize(InputInterface $input, AnnotationData $annotationData);
}
<?php
namespace Consolidation\AnnotatedCommand;
/**
* If an annotated command method returns an object that
* implements OutputDataInterface, then the getOutputData()
* method is used to fetch the output to print from the
* result object.
*/
interface OutputDataInterface
{
public function getOutputData();
}
<?php
namespace Consolidation\AnnotatedCommand;
/**
* Command cration listeners can be added to the annotation
* command factory. These will be notified whenever a new
* commandfile is provided to the factory. This is useful for
* initializing new commandfile objects.
*
* @see AnnotatedCommandFactory::addListener()
*/
class CommandCreationListener implements CommandCreationListenerInterface
{
protected $listener;
public function __construct($listener)
{
$this->listener = $listener;
}
public function notifyCommandFileAdded($command)
{
call_user_func($this->listener, $command);
}
}
<?php
namespace Consolidation\AnnotatedCommand\Events;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
trait CustomEventAwareTrait
{
/** var HookManager */
protected $hookManager;
/**
* {@inheritdoc}
*/
public function setHookManager(HookManager $hookManager)
{
$this->hookManager = $hookManager;
}
/**
* {@inheritdoc}
*/
public function getCustomEventHandlers($eventName)
{
if (!$this->hookManager) {
return [];
}
return $this->hookManager->getHook($eventName, HookManager::ON_EVENT);
}
}
<?php
namespace Consolidation\AnnotatedCommand\Events;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
interface CustomEventAwareInterface
{
/**
* Set a reference to the hook manager for later use
* @param HookManager $hookManager
*/
public function setHookManager(HookManager $hookManager);
/**
* Get all of the defined event handlers of the specified name.
* @param string $eventName
* @return Callable[]
*/
public function getCustomEventHandlers($eventName);
}
<?php
namespace Consolidation\AnnotatedCommand;
/**
* Return a CommandResult as the result of a command to pass both an exit
* code and result data from a command.
*
* Usage:
*
* return CommandResult::dataWithExitCode(new RowsOfFields($rows), 1);
*
* The CommandResult can also be used to unambiguously return just
* an exit code or just output data.
*
* Exit code only:
*
* return CommandResult::dataWithExitCode(1);
*
* Data only:
*
* return CommandResult::data(new RowsOfFields($rows));
*
* Historically, it has always been possible to return an integer to indicate
* that the result is an exit code, and other return types (typically array
* / ArrayObjects) indicating actual data with an implicit exit code of 0.
* Using a CommandResult is preferred, though, as it allows the result of the
* function to be unambiguously specified without type-based interpretation.
*
* @package Consolidation\AnnotatedCommand
*/
class CommandResult implements ExitCodeInterface, OutputDataInterface
{
protected $data;
protected $exitCode;
protected function __construct($data = null, $exitCode = 0)
{
$this->data = $data;
$this->exitCode = $exitCode;
}
public static function exitCode($exitCode)
{
return new self(null, $exitCode);
}
public static function data($data)
{
return new self($data);
}
public static function dataWithExitCode($data, $exitCode)
{
return new self($data, $exitCode);
}
public function getExitCode()
{
return $this->exitCode;
}
public function getOutputData()
{
return $this->data;
}
public function setOutputData($data)
{
$this->data = $data;
}
}
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Cache\CacheWrapper;
use Consolidation\AnnotatedCommand\Cache\NullCache;
use Consolidation\AnnotatedCommand\Cache\SimpleCacheInterface;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Options\AutomaticOptionsProviderInterface;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\AnnotatedCommand\Parser\CommandInfoDeserializer;
use Consolidation\AnnotatedCommand\Parser\CommandInfoSerializer;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* The AnnotatedCommandFactory creates commands for your application.
* Use with a Dependency Injection Container and the CommandFactory.
* Alternately, use the CommandFileDiscovery to find commandfiles, and
* then use AnnotatedCommandFactory::createCommandsFromClass() to create
* commands. See the README for more information.
*
* @package Consolidation\AnnotatedCommand
*/
class AnnotatedCommandFactory implements AutomaticOptionsProviderInterface
{
/** var CommandProcessor */
protected $commandProcessor;
/** var CommandCreationListenerInterface[] */
protected $listeners = [];
/** var AutomaticOptionsProvider[] */
protected $automaticOptionsProviderList = [];
/** var boolean */
protected $includeAllPublicMethods = true;
/** var CommandInfoAltererInterface */
protected $commandInfoAlterers = [];
/** var SimpleCacheInterface */
protected $dataStore;
public function __construct()
{
$this->dataStore = new NullCache();
$this->commandProcessor = new CommandProcessor(new HookManager());
$this->addAutomaticOptionProvider($this);
}
public function setCommandProcessor(CommandProcessor $commandProcessor)
{
$this->commandProcessor = $commandProcessor;
return $this;
}
/**
* @return CommandProcessor
*/
public function commandProcessor()
{
return $this->commandProcessor;
}
/**
* Set the 'include all public methods flag'. If true (the default), then
* every public method of each commandFile will be used to create commands.
* If it is false, then only those public methods annotated with @command
* or @name (deprecated) will be used to create commands.
*/
public function setIncludeAllPublicMethods($includeAllPublicMethods)
{
$this->includeAllPublicMethods = $includeAllPublicMethods;
return $this;
}
public function getIncludeAllPublicMethods()
{
return $this->includeAllPublicMethods;
}
/**
* @return HookManager
*/
public function hookManager()
{
return $this->commandProcessor()->hookManager();
}
/**
* Add a listener that is notified immediately before the command
* factory creates commands from a commandFile instance. This
* listener can use this opportunity to do more setup for the commandFile,
* and so on.
*
* @param CommandCreationListenerInterface $listener
*/
public function addListener(CommandCreationListenerInterface $listener)
{
$this->listeners[] = $listener;
return $this;
}
/**
* Add a listener that's just a simple 'callable'.
* @param callable $listener
*/
public function addListernerCallback(callable $listener)
{
$this->addListener(new CommandCreationListener($listener));
return $this;
}
/**
* Call all command creation listeners
*
* @param object $commandFileInstance
*/
protected function notify($commandFileInstance)
{
foreach ($this->listeners as $listener) {
$listener->notifyCommandFileAdded($commandFileInstance);
}
}
public function addAutomaticOptionProvider(AutomaticOptionsProviderInterface $optionsProvider)
{
$this->automaticOptionsProviderList[] = $optionsProvider;
}
public function addCommandInfoAlterer(CommandInfoAltererInterface $alterer)
{
$this->commandInfoAlterers[] = $alterer;
}
/**
* n.b. This registers all hooks from the commandfile instance as a side-effect.
*/
public function createCommandsFromClass($commandFileInstance, $includeAllPublicMethods = null)
{
// Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor.
if (!isset($includeAllPublicMethods)) {
$includeAllPublicMethods = $this->getIncludeAllPublicMethods();
}
$this->notify($commandFileInstance);
$commandInfoList = $this->getCommandInfoListFromClass($commandFileInstance);
$this->registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance);
return $this->createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods);
}
public function getCommandInfoListFromClass($commandFileInstance)
{
$cachedCommandInfoList = $this->getCommandInfoListFromCache($commandFileInstance);
$commandInfoList = $this->createCommandInfoListFromClass($commandFileInstance, $cachedCommandInfoList);
if (!empty($commandInfoList)) {
$cachedCommandInfoList = array_merge($commandInfoList, $cachedCommandInfoList);
$this->storeCommandInfoListInCache($commandFileInstance, $cachedCommandInfoList);
}
return $cachedCommandInfoList;
}
protected function storeCommandInfoListInCache($commandFileInstance, $commandInfoList)
{
if (!$this->hasDataStore()) {
return;
}
$cache_data = [];
$serializer = new CommandInfoSerializer();
foreach ($commandInfoList as $i => $commandInfo) {
$cache_data[$i] = $serializer->serialize($commandInfo);
}
$className = get_class($commandFileInstance);
$this->getDataStore()->set($className, $cache_data);
}
/**
* Get the command info list from the cache
*
* @param mixed $commandFileInstance
* @return array
*/
protected function getCommandInfoListFromCache($commandFileInstance)
{
$commandInfoList = [];
if (!is_object($commandFileInstance)) {
return [];
}
$className = get_class($commandFileInstance);
if (!$this->getDataStore()->has($className)) {
return [];
}
$deserializer = new CommandInfoDeserializer();
$cache_data = $this->getDataStore()->get($className);
foreach ($cache_data as $i => $data) {
if (CommandInfoDeserializer::isValidSerializedData((array)$data)) {
$commandInfoList[$i] = $deserializer->deserialize((array)$data);
}
}
return $commandInfoList;
}
/**
* Check to see if this factory has a cache datastore.
* @return boolean
*/
public function hasDataStore()
{
return !($this->dataStore instanceof NullCache);
}
/**
* Set a cache datastore for this factory. Any object with 'set' and
* 'get' methods is acceptable. The key is the classname being cached,
* and the value is a nested associative array of strings.
*
* TODO: Typehint this to SimpleCacheInterface
*
* This is not done currently to allow clients to use a generic cache
* store that does not itself depend on the annotated-command library.
*
* @param Mixed $dataStore
* @return type
*/
public function setDataStore($dataStore)
{
if (!($dataStore instanceof SimpleCacheInterface)) {
$dataStore = new CacheWrapper($dataStore);
}
$this->dataStore = $dataStore;
return $this;
}
/**
* Get the data store attached to this factory.
*/
public function getDataStore()
{
return $this->dataStore;
}
protected function createCommandInfoListFromClass($classNameOrInstance, $cachedCommandInfoList)
{
$commandInfoList = [];
// Ignore special functions, such as __construct and __call, which
// can never be commands.
$commandMethodNames = array_filter(
get_class_methods($classNameOrInstance) ?: [],
function ($m) use ($classNameOrInstance) {
$reflectionMethod = new \ReflectionMethod($classNameOrInstance, $m);
return !$reflectionMethod->isStatic() && !preg_match('#^_#', $m);
}
);
foreach ($commandMethodNames as $commandMethodName) {
if (!array_key_exists($commandMethodName, $cachedCommandInfoList)) {
$commandInfo = CommandInfo::create($classNameOrInstance, $commandMethodName);
if (!static::isCommandOrHookMethod($commandInfo, $this->getIncludeAllPublicMethods())) {
$commandInfo->invalidate();
}
$commandInfoList[$commandMethodName] = $commandInfo;
}
}
return $commandInfoList;
}
public function createCommandInfo($classNameOrInstance, $commandMethodName)
{
return CommandInfo::create($classNameOrInstance, $commandMethodName);
}
public function createCommandsFromClassInfo($commandInfoList, $commandFileInstance, $includeAllPublicMethods = null)
{
// Deprecated: avoid using the $includeAllPublicMethods in favor of the setIncludeAllPublicMethods() accessor.
if (!isset($includeAllPublicMethods)) {
$includeAllPublicMethods = $this->getIncludeAllPublicMethods();
}
return $this->createSelectedCommandsFromClassInfo(
$commandInfoList,
$commandFileInstance,
function ($commandInfo) use ($includeAllPublicMethods) {
return static::isCommandMethod($commandInfo, $includeAllPublicMethods);
}
);
}
public function createSelectedCommandsFromClassInfo($commandInfoList, $commandFileInstance, callable $commandSelector)
{
$commandInfoList = $this->filterCommandInfoList($commandInfoList, $commandSelector);
return array_map(
function ($commandInfo) use ($commandFileInstance) {
return $this->createCommand($commandInfo, $commandFileInstance);
},
$commandInfoList
);
}
protected function filterCommandInfoList($commandInfoList, callable $commandSelector)
{
return array_filter($commandInfoList, $commandSelector);
}
public static function isCommandOrHookMethod($commandInfo, $includeAllPublicMethods)
{
return static::isHookMethod($commandInfo) || static::isCommandMethod($commandInfo, $includeAllPublicMethods);
}
public static function isHookMethod($commandInfo)
{
return $commandInfo->hasAnnotation('hook');
}
public static function isCommandMethod($commandInfo, $includeAllPublicMethods)
{
// Ignore everything labeled @hook
if (static::isHookMethod($commandInfo)) {
return false;
}
// Include everything labeled @command
if ($commandInfo->hasAnnotation('command')) {
return true;
}
// Skip anything that has a missing or invalid name.
$commandName = $commandInfo->getName();
if (empty($commandName) || preg_match('#[^a-zA-Z0-9:_-]#', $commandName)) {
return false;
}
// Skip anything named like an accessor ('get' or 'set')
if (preg_match('#^(get[A-Z]|set[A-Z])#', $commandInfo->getMethodName())) {
return false;
}
// Default to the setting of 'include all public methods'.
return $includeAllPublicMethods;
}
public function registerCommandHooksFromClassInfo($commandInfoList, $commandFileInstance)
{
foreach ($commandInfoList as $commandInfo) {
if (static::isHookMethod($commandInfo)) {
$this->registerCommandHook($commandInfo, $commandFileInstance);
}
}
}
/**
* Register a command hook given the CommandInfo for a method.
*
* The hook format is:
*
* @hook type name type
*
* For example, the pre-validate hook for the core:init command is:
*
* @hook pre-validate core:init
*
* If no command name is provided, then this hook will affect every
* command that is defined in the same file.
*
* If no hook is provided, then we will presume that ALTER_RESULT
* is intended.
*
* @param CommandInfo $commandInfo Information about the command hook method.
* @param object $commandFileInstance An instance of the CommandFile class.
*/
public function registerCommandHook(CommandInfo $commandInfo, $commandFileInstance)
{
// Ignore if the command info has no @hook
if (!static::isHookMethod($commandInfo)) {
return;
}
$hookData = $commandInfo->getAnnotation('hook');
$hook = $this->getNthWord($hookData, 0, HookManager::ALTER_RESULT);
$commandName = $this->getNthWord($hookData, 1);
// Register the hook
$callback = [$commandFileInstance, $commandInfo->getMethodName()];
$this->commandProcessor()->hookManager()->add($callback, $hook, $commandName);
// If the hook has options, then also register the commandInfo
// with the hook manager, so that we can add options and such to
// the commands they hook.
if (!$commandInfo->options()->isEmpty()) {
$this->commandProcessor()->hookManager()->recordHookOptions($commandInfo, $commandName);
}
}
protected function getNthWord($string, $n, $default = '', $delimiter = ' ')
{
$words = explode($delimiter, $string);
if (!empty($words[$n])) {
return $words[$n];
}
return $default;
}
public function createCommand(CommandInfo $commandInfo, $commandFileInstance)
{
$this->alterCommandInfo($commandInfo, $commandFileInstance);
$command = new AnnotatedCommand($commandInfo->getName());
$commandCallback = [$commandFileInstance, $commandInfo->getMethodName()];
$command->setCommandCallback($commandCallback);
$command->setCommandProcessor($this->commandProcessor);
$command->setCommandInfo($commandInfo);
$automaticOptions = $this->callAutomaticOptionsProviders($commandInfo);
$command->setCommandOptions($commandInfo, $automaticOptions);
// Annotation commands are never bootstrap-aware, but for completeness
// we will notify on every created command, as some clients may wish to
// use this notification for some other purpose.
$this->notify($command);
return $command;
}
/**
* Give plugins an opportunity to update the commandInfo
*/
public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance)
{
foreach ($this->commandInfoAlterers as $alterer) {
$alterer->alterCommandInfo($commandInfo, $commandFileInstance);
}
}
/**
* Get the options that are implied by annotations, e.g. @fields implies
* that there should be a --fields and a --format option.
*
* @return InputOption[]
*/
public function callAutomaticOptionsProviders(CommandInfo $commandInfo)
{
$automaticOptions = [];
foreach ($this->automaticOptionsProviderList as $automaticOptionsProvider) {
$automaticOptions += $automaticOptionsProvider->automaticOptions($commandInfo);
}
return $automaticOptions;
}
/**
* Get the options that are implied by annotations, e.g. @fields implies
* that there should be a --fields and a --format option.
*
* @return InputOption[]
*/
public function automaticOptions(CommandInfo $commandInfo)
{
$automaticOptions = [];
$formatManager = $this->commandProcessor()->formatterManager();
if ($formatManager) {
$annotationData = $commandInfo->getAnnotations()->getArrayCopy();
$formatterOptions = new FormatterOptions($annotationData);
$dataType = $commandInfo->getReturnType();
$automaticOptions = $formatManager->automaticOptions($formatterOptions, $dataType);
}
return $automaticOptions;
}
}
<?php
namespace Consolidation\AnnotatedCommand\Help;
use Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
class HelpDocument implements DomDataInterface
{
/** var Command */
protected $command;
/** var \DOMDocument */
protected $dom;
/**
* Create a help document from a Symfony Console command
*/
public function __construct(Command $command)
{
$dom = $this->generateBaseHelpDom($command);
$dom = $this->alterHelpDocument($command, $dom);
$this->command = $command;
$this->dom = $dom;
}
/**
* Convert data into a \DomDocument.
*
* @return \DomDocument
*/
public function getDomData()
{
return $this->dom;
}
/**
* Create the base help DOM prior to alteration by the Command object.
* @param Command $command
* @return \DomDocument
*/
protected function generateBaseHelpDom(Command $command)
{
// Use Symfony to generate xml text. If other formats are
// requested, convert from xml to the desired form.
$descriptor = new XmlDescriptor();
return $descriptor->getCommandDocument($command);
}
/**
* Alter the DOM document per the command object
* @param Command $command
* @param \DomDocument $dom
* @return \DomDocument
*/
protected function alterHelpDocument(Command $command, \DomDocument $dom)
{
if ($command instanceof HelpDocumentAlter) {
$dom = $command->helpAlter($dom);
}
return $dom;
}
}
<?php
namespace Consolidation\AnnotatedCommand\Help;
interface HelpDocumentAlter
{
public function helpAlter(\DomDocument $dom);
}
<?php
namespace Consolidation\AnnotatedCommand\Help;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class HelpCommand
{
/** var Application */
protected $application;
/**
* Create a help document from a Symfony Console command
*/
public function __construct(Application $application)
{
$this->application = $application;
}
public function getApplication()
{
return $this->application;
}
/**
* Run the help command
*
* @command my-help
* @return \Consolidation\AnnotatedCommand\Help\HelpDocument
*/
public function help($commandName = 'help')
{
$command = $this->getApplication()->find($commandName);
$helpDocument = $this->getHelpDocument($command);
return $helpDocument;
}
/**
* Create a help document.
*/
protected function getHelpDocument($command)
{
return new HelpDocument($command);
}
}
<?php
namespace Consolidation\AnnotatedCommand\Help;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Consolidation\AnnotatedCommand\AnnotatedCommand;
class HelpDocumentBuilder
{
public static function alter(\DomDocument $originalDom, AnnotatedCommand $command)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($commandXML = $dom->createElement('command'));
$commandXML->setAttribute('id', $command->getName());
$commandXML->setAttribute('name', $command->getName());
// Get the original <command> element and its top-level elements.
$originalCommandXML = static::getSingleElementByTagName($dom, $originalDom, 'command');
$originalUsagesXML = static::getSingleElementByTagName($dom, $originalCommandXML, 'usages');
$originalDescriptionXML = static::getSingleElementByTagName($dom, $originalCommandXML, 'description');
$originalHelpXML = static::getSingleElementByTagName($dom, $originalCommandXML, 'help');
$originalArgumentsXML = static::getSingleElementByTagName($dom, $originalCommandXML, 'arguments');
$originalOptionsXML = static::getSingleElementByTagName($dom, $originalCommandXML, 'options');
// Keep only the first of the <usage> elements
$newUsagesXML = $dom->createElement('usages');
$firstUsageXML = static::getSingleElementByTagName($dom, $originalUsagesXML, 'usage');
$newUsagesXML->appendChild($firstUsageXML);
// Create our own <example> elements
$newExamplesXML = $dom->createElement('examples');
foreach ($command->getExampleUsages() as $usage => $description) {
$newExamplesXML->appendChild($exampleXML = $dom->createElement('example'));
$exampleXML->appendChild($usageXML = $dom->createElement('usage', $usage));
$exampleXML->appendChild($descriptionXML = $dom->createElement('description', $description));
}
// Create our own <alias> elements
$newAliasesXML = $dom->createElement('aliases');
foreach ($command->getAliases() as $alias) {
$newAliasesXML->appendChild($dom->createElement('alias', $alias));
}
// Create our own <topic> elements
$newTopicsXML = $dom->createElement('topics');
foreach ($command->getTopics() as $topic) {
$newTopicsXML->appendChild($topicXML = $dom->createElement('topic', $topic));
}
// Place the different elements into the <command> element in the desired order
$commandXML->appendChild($newUsagesXML);
$commandXML->appendChild($newExamplesXML);
$commandXML->appendChild($originalDescriptionXML);
$commandXML->appendChild($originalArgumentsXML);
$commandXML->appendChild($originalOptionsXML);
$commandXML->appendChild($originalHelpXML);
$commandXML->appendChild($newAliasesXML);
$commandXML->appendChild($newTopicsXML);
return $dom;
}
protected static function getSingleElementByTagName($dom, $parent, $tagName)
{
// There should always be exactly one '<command>' element.
$elements = $parent->getElementsByTagName($tagName);
$result = $elements->item(0);
$result = $dom->importNode($result, true);
return $result;
}
}
<?php
namespace Consolidation\OutputFormatters\Options;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface OverrideOptionsInterface
{
/**
* Allow the formatter to mess with the configuration options before any
* transformations et. al. get underway.
*
* @param mixed $structuredOutput Data to restructure
* @param FormatterOptions $options Formatting options
* @return FormatterOptions
*/
public function overrideOptions($structuredOutput, FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\Options;
use Symfony\Component\Console\Input\InputInterface;
use Consolidation\OutputFormatters\Transformations\PropertyParser;
use Consolidation\OutputFormatters\StructuredData\Xml\XmlSchema;
use Consolidation\OutputFormatters\StructuredData\Xml\XmlSchemaInterface;
/**
* FormetterOptions holds information that affects the way a formatter
* renders its output.
*
* There are three places where a formatter might get options from:
*
* 1. Configuration associated with the command that produced the output.
* This is passed in to FormatterManager::write() along with the data
* to format. It might originally come from annotations on the command,
* or it might come from another source. Examples include the field labels
* for a table, or the default list of fields to display.
*
* 2. Options specified by the user, e.g. by commandline options.
*
* 3. Default values associated with the formatter itself.
*
* This class caches configuration from sources (1) and (2), and expects
* to be provided the defaults, (3), whenever a value is requested.
*/
class FormatterOptions
{
/** var array */
protected $configurationData = [];
/** var array */
protected $options = [];
/** var InputInterface */
protected $input;
const FORMAT = 'format';
const DEFAULT_FORMAT = 'default-format';
const TABLE_STYLE = 'table-style';
const LIST_ORIENTATION = 'list-orientation';
const FIELDS = 'fields';
const FIELD = 'field';
const INCLUDE_FIELD_LABELS = 'include-field-labels';
const ROW_LABELS = 'row-labels';
const FIELD_LABELS = 'field-labels';
const DEFAULT_FIELDS = 'default-fields';
const DEFAULT_TABLE_FIELDS = 'default-table-fields';
const DEFAULT_STRING_FIELD = 'default-string-field';
const DELIMITER = 'delimiter';
const CSV_ENCLOSURE = 'csv-enclosure';
const CSV_ESCAPE_CHAR = 'csv-escape-char';
const LIST_DELIMITER = 'list-delimiter';
const TERMINAL_WIDTH = 'width';
const METADATA_TEMPLATE = 'metadata-template';
const HUMAN_READABLE = 'human-readable';
/**
* Create a new FormatterOptions with the configuration data and the
* user-specified options for this request.
*
* @see FormatterOptions::setInput()
* @param array $configurationData
* @param array $options
*/
public function __construct($configurationData = [], $options = [])
{
$this->configurationData = $configurationData;
$this->options = $options;
}
/**
* Create a new FormatterOptions object with new configuration data (provided),
* and the same options data as this instance.
*
* @param array $configurationData
* @return FormatterOptions
*/
public function override($configurationData)
{
$override = new self();
$override
->setConfigurationData($configurationData + $this->getConfigurationData())
->setOptions($this->getOptions());
return $override;
}
public function setTableStyle($style)
{
return $this->setConfigurationValue(self::TABLE_STYLE, $style);
}
public function setDelimiter($delimiter)
{
return $this->setConfigurationValue(self::DELIMITER, $delimiter);
}
public function setCsvEnclosure($enclosure)
{
return $this->setConfigurationValue(self::CSV_ENCLOSURE, $enclosure);
}
public function setCsvEscapeChar($escapeChar)
{
return $this->setConfigurationValue(self::CSV_ESCAPE_CHAR, $escapeChar);
}
public function setListDelimiter($listDelimiter)
{
return $this->setConfigurationValue(self::LIST_DELIMITER, $listDelimiter);
}
public function setIncludeFieldLables($includFieldLables)
{
return $this->setConfigurationValue(self::INCLUDE_FIELD_LABELS, $includFieldLables);
}
public function setListOrientation($listOrientation)
{
return $this->setConfigurationValue(self::LIST_ORIENTATION, $listOrientation);
}
public function setRowLabels($rowLabels)
{
return $this->setConfigurationValue(self::ROW_LABELS, $rowLabels);
}
public function setDefaultFields($fields)
{
return $this->setConfigurationValue(self::DEFAULT_FIELDS, $fields);
}
public function setFieldLabels($fieldLabels)
{
return $this->setConfigurationValue(self::FIELD_LABELS, $fieldLabels);
}
public function setDefaultStringField($defaultStringField)
{
return $this->setConfigurationValue(self::DEFAULT_STRING_FIELD, $defaultStringField);
}
public function setWidth($width)
{
return $this->setConfigurationValue(self::TERMINAL_WIDTH, $width);
}
public function setHumanReadable($isHumanReadable = true)
{
return $this->setConfigurationValue(self::HUMAN_READABLE, $isHumanReadable);
}
/**
* Get a formatter option
*
* @param string $key
* @param array $defaults
* @param mixed $default
* @return mixed
*/
public function get($key, $defaults = [], $default = false)
{
$value = $this->fetch($key, $defaults, $default);
return $this->parse($key, $value);
}
/**
* Return the XmlSchema to use with --format=xml for data types that support
* that. This is used when an array needs to be converted into xml.
*
* @return XmlSchema
*/
public function getXmlSchema()
{
return new XmlSchema();
}
/**
* Determine the format that was requested by the caller.
*
* @param array $defaults
* @return string
*/
public function getFormat($defaults = [])
{
return $this->get(self::FORMAT, [], $this->get(self::DEFAULT_FORMAT, $defaults, ''));
}
/**
* Look up a key, and return its raw value.
*
* @param string $key
* @param array $defaults
* @param mixed $default
* @return mixed
*/
protected function fetch($key, $defaults = [], $default = false)
{
$defaults = $this->defaultsForKey($key, $defaults, $default);
$values = $this->fetchRawValues($defaults);
return $values[$key];
}
/**
* Reduce provided defaults to the single item identified by '$key',
* if it exists, or an empty array otherwise.
*
* @param string $key
* @param array $defaults
* @return array
*/
protected function defaultsForKey($key, $defaults, $default = false)
{
if (array_key_exists($key, $defaults)) {
return [$key => $defaults[$key]];
}
return [$key => $default];
}
/**
* Look up all of the items associated with the provided defaults.
*
* @param array $defaults
* @return array
*/
protected function fetchRawValues($defaults = [])
{
return array_merge(
$defaults,
$this->getConfigurationData(),
$this->getOptions(),
$this->getInputOptions($defaults)
);
}
/**
* Given the raw value for a specific key, do any type conversion
* (e.g. from a textual list to an array) needed for the data.
*
* @param string $key
* @param mixed $value
* @return mixed
*/
protected function parse($key, $value)
{
$optionFormat = $this->getOptionFormat($key);
if (!empty($optionFormat) && is_string($value)) {
return $this->$optionFormat($value);
}
return $value;
}
/**
* Convert from a textual list to an array
*
* @param string $value
* @return array
*/
public function parsePropertyList($value)
{
return PropertyParser::parse($value);
}
/**
* Given a specific key, return the class method name of the
* parsing method for data stored under this key.
*
* @param string $key
* @return string
*/
protected function getOptionFormat($key)
{
$propertyFormats = [
self::ROW_LABELS => 'PropertyList',
self::FIELD_LABELS => 'PropertyList',
];
if (array_key_exists($key, $propertyFormats)) {
return "parse{$propertyFormats[$key]}";
}
return '';
}
/**
* Change the configuration data for this formatter options object.
*
* @param array $configurationData
* @return FormatterOptions
*/
public function setConfigurationData($configurationData)
{
$this->configurationData = $configurationData;
return $this;
}
/**
* Change one configuration value for this formatter option.
*
* @param string $key
* @param mixed $value
* @return FormetterOptions
*/
protected function setConfigurationValue($key, $value)
{
$this->configurationData[$key] = $value;
return $this;
}
/**
* Change one configuration value for this formatter option, but only
* if it does not already have a value set.
*
* @param string $key
* @param mixed $value
* @return FormetterOptions
*/
public function setConfigurationDefault($key, $value)
{
if (!array_key_exists($key, $this->configurationData)) {
return $this->setConfigurationValue($key, $value);
}
return $this;
}
/**
* Return a reference to the configuration data for this object.
*
* @return array
*/
public function getConfigurationData()
{
return $this->configurationData;
}
/**
* Set all of the options that were specified by the user for this request.
*
* @param array $options
* @return FormatterOptions
*/
public function setOptions($options)
{
$this->options = $options;
return $this;
}
/**
* Change one option value specified by the user for this request.
*
* @param string $key
* @param mixed $value
* @return FormatterOptions
*/
public function setOption($key, $value)
{
$this->options[$key] = $value;
return $this;
}
/**
* Return a reference to the user-specified options for this request.
*
* @return array
*/
public function getOptions()
{
return $this->options;
}
/**
* Provide a Symfony Console InputInterface containing the user-specified
* options for this request.
*
* @param InputInterface $input
* @return type
*/
public function setInput(InputInterface $input)
{
$this->input = $input;
}
/**
* Return all of the options from the provided $defaults array that
* exist in our InputInterface object.
*
* @param array $defaults
* @return array
*/
public function getInputOptions($defaults)
{
if (!isset($this->input)) {
return [];
}
$options = [];
foreach ($defaults as $key => $value) {
if ($this->input->hasOption($key)) {
$result = $this->input->getOption($key);
if (isset($result)) {
$options[$key] = $this->input->getOption($key);
}
}
}
return $options;
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface ListDataInterface
{
/**
* Convert data to a format suitable for use in a list.
* By default, the array values will be used. Implement
* ListDataInterface to use some other criteria (e.g. array keys).
*
* @return array
*/
public function getListData(FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Formatters\FormatterAwareInterface;
use Consolidation\OutputFormatters\Formatters\FormatterAwareTrait;
trait RenderCellCollectionTrait
{
use FormatterAwareTrait;
/** @var RenderCellInterface[] */
protected $rendererList = [
RenderCellCollectionInterface::PRIORITY_FIRST => [],
RenderCellCollectionInterface::PRIORITY_NORMAL => [],
RenderCellCollectionInterface::PRIORITY_FALLBACK => [],
];
/**
* Add a renderer
*
* @return $this
*/
public function addRenderer(RenderCellInterface $renderer, $priority = RenderCellCollectionInterface::PRIORITY_NORMAL)
{
$this->rendererList[$priority][] = $renderer;
return $this;
}
/**
* Add a callable as a renderer
*
* @return $this
*/
public function addRendererFunction(callable $rendererFn, $priority = RenderCellCollectionInterface::PRIORITY_NORMAL)
{
$renderer = new CallableRenderer($rendererFn);
return $this->addRenderer($renderer, $priority);
}
/**
* {@inheritdoc}
*/
public function renderCell($key, $cellData, FormatterOptions $options, $rowData)
{
$flattenedRendererList = array_reduce(
$this->rendererList,
function ($carry, $item) {
return array_merge($carry, $item);
},
[]
);
foreach ($flattenedRendererList as $renderer) {
if ($renderer instanceof FormatterAwareInterface) {
$renderer->setFormatter($this->getFormatter());
}
$cellData = $renderer->renderCell($key, $cellData, $options, $rowData);
}
return $cellData;
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
interface TableDataInterface
{
/**
* Convert structured data into a form suitable for use
* by the table formatter.
*
* @param boolean $includeRowKey Add a field containing the
* key from each row.
*
* @return array
*/
public function getTableData($includeRowKey = false);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface;
class HelpDocument implements DomDataInterface
{
/**
* Convert data into a \DomDocument.
*
* @return \DomDocument
*/
public function getDomData()
{
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
interface OriginalDataInterface
{
/**
* Return the original data for this table. Used by any
* formatter that expects an array.
*/
public function getOriginalData();
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
use Consolidation\OutputFormatters\Transformations\UnstructuredDataTransformation;
/**
* Represents aribtrary unstructured array data where the
* data to display in --list format comes from the array keys.
*
* Unstructured list data can have variable keys in every rown (unlike
* RowsOfFields, which expects uniform rows), and the data elements may
* themselves be deep arrays.
*/
class UnstructuredData extends AbstractListData implements UnstructuredInterface, RestructureInterface
{
public function __construct($data)
{
parent::__construct($data);
}
public function restructure(FormatterOptions $options)
{
$defaults = $this->defaultOptions();
$fields = $this->getFields($options, $defaults);
return new UnstructuredDataTransformation($this->getArrayCopy(), FieldProcessor::processFieldAliases($fields));
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
/**
* Holds an array where each element of the array is one row,
* and each row contains an associative array where the keys
* are the field names, and the values are the field data.
*
* It is presumed that every row contains the same keys.
*/
class RowsOfFields extends AbstractStructuredList implements ConversionInterface
{
/**
* @inheritdoc
*/
public function convert(FormatterOptions $options)
{
$defaults = $this->defaultOptions();
$fields = $this->getFields($options, $defaults);
if (FieldProcessor::hasUnstructuredFieldAccess($fields)) {
return new UnstructuredListData($this->getArrayCopy());
}
return $this;
}
/**
* Restructure this data for output by converting it into a table
* transformation object.
*
* @param FormatterOptions $options Options that affect output formatting.
* @return Consolidation\OutputFormatters\Transformations\TableTransformation
*/
public function restructure(FormatterOptions $options)
{
$data = $this->getArrayCopy();
return $this->createTableTransformation($data, $options);
}
public function getListData(FormatterOptions $options)
{
return array_keys($this->getArrayCopy());
}
protected function defaultOptions()
{
return [
FormatterOptions::LIST_ORIENTATION => false,
] + parent::defaultOptions();
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface RestructureInterface
{
/**
* Allow structured data to be restructured -- i.e. to select fields
* to show, reorder fields, etc.
*
* @param FormatterOptions $options Formatting options
*/
public function restructure(FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\ListDataInterface;
use Consolidation\OutputFormatters\Transformations\TableTransformation;
/**
* Holds an array where each element of the array is one row,
* and each row contains an associative array where the keys
* are the field names, and the values are the field data.
*
* It is presumed that every row contains the same keys.
*/
abstract class AbstractStructuredList extends AbstractListData implements RestructureInterface, RenderCellCollectionInterface
{
use RenderCellCollectionTrait;
public function __construct($data)
{
parent::__construct($data);
}
abstract public function restructure(FormatterOptions $options);
protected function createTableTransformation($data, $options)
{
$defaults = $this->defaultOptions();
$fieldLabels = $this->getReorderedFieldLabels($data, $options, $defaults);
$tableTransformer = $this->instantiateTableTransformation($data, $fieldLabels, $options->get(FormatterOptions::ROW_LABELS, $defaults));
if ($options->get(FormatterOptions::LIST_ORIENTATION, $defaults)) {
$tableTransformer->setLayout(TableTransformation::LIST_LAYOUT);
}
return $tableTransformer;
}
protected function instantiateTableTransformation($data, $fieldLabels, $rowLabels)
{
return new TableTransformation($data, $fieldLabels, $rowLabels);
}
protected function defaultOptions()
{
return [
FormatterOptions::ROW_LABELS => [],
FormatterOptions::DEFAULT_FIELDS => [],
] + parent::defaultOptions();
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
/**
* @deprecated Use UnstructuredListData
*/
class ListDataFromKeys extends AbstractListData
{
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
use Consolidation\OutputFormatters\Transformations\UnstructuredDataListTransformation;
/**
* Represents aribtrary unstructured array data where the
* data to display in --list format comes from the array keys.
*
* Unstructured list data can have variable keys in every rown (unlike
* RowsOfFields, which expects uniform rows), and the data elements may
* themselves be deep arrays.
*/
class UnstructuredListData extends AbstractListData implements UnstructuredInterface, RestructureInterface
{
public function __construct($data)
{
parent::__construct($data);
}
public function restructure(FormatterOptions $options)
{
$defaults = $this->defaultOptions();
$fields = $this->getFields($options, $defaults);
return new UnstructuredDataListTransformation($this->getArrayCopy(), FieldProcessor::processFieldAliases($fields));
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
/**
* UnstructuredInterface is a marker interface that indicates that the
* data type is unstructured, and has no default conversion to a string.
* Unstructured data supports the `string` format only if it also implements
* StringTransformationInterface.
*/
interface UnstructuredInterface
{
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\ListDataInterface;
use Consolidation\OutputFormatters\Transformations\PropertyParser;
use Consolidation\OutputFormatters\Transformations\ReorderFields;
use Consolidation\OutputFormatters\Transformations\TableTransformation;
use Consolidation\OutputFormatters\Transformations\PropertyListTableTransformation;
/**
* Holds an array where each element of the array is one
* key : value pair. The keys must be unique, as is typically
* the case for associative arrays.
*/
class PropertyList extends AbstractStructuredList implements ConversionInterface
{
/**
* @inheritdoc
*/
public function convert(FormatterOptions $options)
{
$defaults = $this->defaultOptions();
$fields = $this->getFields($options, $defaults);
if (FieldProcessor::hasUnstructuredFieldAccess($fields)) {
return new UnstructuredData($this->getArrayCopy());
}
return $this;
}
/**
* Restructure this data for output by converting it into a table
* transformation object.
*
* @param FormatterOptions $options Options that affect output formatting.
* @return Consolidation\OutputFormatters\Transformations\TableTransformation
*/
public function restructure(FormatterOptions $options)
{
$data = [$this->getArrayCopy()];
$options->setConfigurationDefault('list-orientation', true);
$tableTransformer = $this->createTableTransformation($data, $options);
return $tableTransformer;
}
public function getListData(FormatterOptions $options)
{
$data = $this->getArrayCopy();
$defaults = $this->defaultOptions();
$fieldLabels = $this->getReorderedFieldLabels([$data], $options, $defaults);
$result = [];
foreach ($fieldLabels as $id => $label) {
$result[$id] = $data[$id];
}
return $result;
}
protected function defaultOptions()
{
return [
FormatterOptions::LIST_ORIENTATION => true,
] + parent::defaultOptions();
}
protected function instantiateTableTransformation($data, $fieldLabels, $rowLabels)
{
return new PropertyListTableTransformation($data, $fieldLabels, $rowLabels);
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData\Xml;
/**
* When using arrays, we could represent XML data in a number of
* different ways.
*
* For example, given the following XML data strucutre:
*
* <document id="1" name="doc">
* <foobars>
* <foobar id="123">
* <name>blah</name>
* <widgets>
* <widget>
* <foo>a</foo>
* <bar>b</bar>
* <baz>c</baz>
* </widget>
* </widgets>
* </foobar>
* </foobars>
* </document>
*
* This could be:
*
* [
* 'id' => 1,
* 'name' => 'doc',
* 'foobars' =>
* [
* [
* 'id' => '123',
* 'name' => 'blah',
* 'widgets' =>
* [
* [
* 'foo' => 'a',
* 'bar' => 'b',
* 'baz' => 'c',
* ]
* ],
* ],
* ]
* ]
*
* The challenge is more in going from an array back to the more
* structured xml format. Note that any given key => string mapping
* could represent either an attribute, or a simple XML element
* containing only a string value. In general, we do *not* want to add
* extra layers of nesting in the data structure to disambiguate between
* these kinds of data, as we want the source data to render cleanly
* into other formats, e.g. yaml, json, et. al., and we do not want to
* force every data provider to have to consider the optimal xml schema
* for their data.
*
* Our strategy, therefore, is to expect clients that wish to provide
* a very specific xml representation to return a DOMDocument, and,
* for other data structures where xml is a secondary concern, then we
* will use some default heuristics to convert from arrays to xml.
*/
interface XmlSchemaInterface
{
/**
* Convert data to a format suitable for use in a list.
* By default, the array values will be used. Implement
* ListDataInterface to use some other criteria (e.g. array keys).
*
* @return \DOMDocument
*/
public function arrayToXml($structuredData);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData\Xml;
interface DomDataInterface
{
/**
* Convert data into a \DomDocument.
*
* @return \DomDocument
*/
public function getDomData();
}
<?php
namespace Consolidation\OutputFormatters\StructuredData\Xml;
class XmlSchema implements XmlSchemaInterface
{
protected $elementList;
public function __construct($elementList = [])
{
$defaultElementList =
[
'*' => ['description'],
];
$this->elementList = array_merge_recursive($elementList, $defaultElementList);
}
public function arrayToXML($structuredData)
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$topLevelElement = $this->getTopLevelElementName($structuredData);
$this->addXmlData($dom, $dom, $topLevelElement, $structuredData);
return $dom;
}
protected function addXmlData(\DOMDocument $dom, $xmlParent, $elementName, $structuredData)
{
$element = $dom->createElement($elementName);
$xmlParent->appendChild($element);
if (is_string($structuredData)) {
$element->appendChild($dom->createTextNode($structuredData));
return;
}
$this->addXmlChildren($dom, $element, $elementName, $structuredData);
}
protected function addXmlChildren(\DOMDocument $dom, $xmlParent, $elementName, $structuredData)
{
foreach ($structuredData as $key => $value) {
$this->addXmlDataOrAttribute($dom, $xmlParent, $elementName, $key, $value);
}
}
protected function addXmlDataOrAttribute(\DOMDocument $dom, $xmlParent, $elementName, $key, $value)
{
$childElementName = $this->getDefaultElementName($elementName);
$elementName = $this->determineElementName($key, $childElementName, $value);
if (($elementName != $childElementName) && $this->isAttribute($elementName, $key, $value)) {
$xmlParent->setAttribute($key, $value);
return;
}
$this->addXmlData($dom, $xmlParent, $elementName, $value);
}
protected function determineElementName($key, $childElementName, $value)
{
if (is_numeric($key)) {
return $childElementName;
}
if (is_object($value)) {
$value = (array)$value;
}
if (!is_array($value)) {
return $key;
}
if (array_key_exists('id', $value) && ($value['id'] == $key)) {
return $childElementName;
}
if (array_key_exists('name', $value) && ($value['name'] == $key)) {
return $childElementName;
}
return $key;
}
protected function getTopLevelElementName($structuredData)
{
return 'document';
}
protected function getDefaultElementName($parentElementName)
{
$singularName = $this->singularForm($parentElementName);
if (isset($singularName)) {
return $singularName;
}
return 'item';
}
protected function isAttribute($parentElementName, $elementName, $value)
{
if (!is_string($value)) {
return false;
}
return !$this->inElementList($parentElementName, $elementName) && !$this->inElementList('*', $elementName);
}
protected function inElementList($parentElementName, $elementName)
{
if (!array_key_exists($parentElementName, $this->elementList)) {
return false;
}
return in_array($elementName, $this->elementList[$parentElementName]);
}
protected function singularForm($name)
{
if (substr($name, strlen($name) - 1) == "s") {
return substr($name, 0, strlen($name) - 1);
}
}
protected function isAssoc($data)
{
return array_keys($data) == range(0, count($data));
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface ConversionInterface
{
/**
* Allow structured data to be converted -- i.e. from
* RowsOfFields to UnstructuredListData.
*
* @param FormatterOptions $options Formatting options
*/
public function convert(FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface MetadataHolderInterface
{
public function getDataKey();
public function setDataKey($key);
public function getMetadataKey();
public function setMetadataKey($key);
public function extractData($data);
public function extractMetadata($data);
public function reconstruct($data, $metadata);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface RenderCellInterface
{
/**
* Convert the contents of one table cell into a string,
* so that it may be placed in the table. Renderer should
* return the $cellData passed to it if it does not wish to
* process it.
*
* @param string $key Identifier of the cell being rendered
* @param mixed $cellData The data to render
* @param FormatterOptions $options The formatting options
* @param array $rowData The rest of the row data
*
* @return mixed
*/
public function renderCell($key, $cellData, FormatterOptions $options, $rowData);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
class CallableRenderer implements RenderCellInterface
{
/** @var callable */
protected $renderFunction;
public function __construct(callable $renderFunction)
{
$this->renderFunction = $renderFunction;
}
/**
* {@inheritdoc}
*/
public function renderCell($key, $cellData, FormatterOptions $options, $rowData)
{
return call_user_func($this->renderFunction, $key, $cellData, $options, $rowData);
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Transformations\ReorderFields;
/**
* Base class for all list data types.
*/
class AbstractListData extends \ArrayObject implements ListDataInterface
{
public function __construct($data)
{
parent::__construct($data);
}
public function getListData(FormatterOptions $options)
{
return array_keys($this->getArrayCopy());
}
protected function getReorderedFieldLabels($data, $options, $defaults)
{
$reorderer = new ReorderFields();
$fieldLabels = $reorderer->reorder(
$this->getFields($options, $defaults),
$options->get(FormatterOptions::FIELD_LABELS, $defaults),
$data
);
return $fieldLabels;
}
protected function getFields($options, $defaults)
{
$fieldShortcut = $options->get(FormatterOptions::FIELD);
if (!empty($fieldShortcut)) {
return [$fieldShortcut];
}
$result = $options->get(FormatterOptions::FIELDS);
if (!empty($result)) {
return $result;
}
$isHumanReadable = $options->get(FormatterOptions::HUMAN_READABLE);
if ($isHumanReadable) {
$result = $options->get(FormatterOptions::DEFAULT_TABLE_FIELDS);
if (!empty($result)) {
return $result;
}
}
return $options->get(FormatterOptions::DEFAULT_FIELDS, $defaults);
}
/**
* A structured list may provide its own set of default options. These
* will be used in place of the command's default options (from the
* annotations) in instances where the user does not provide the options
* explicitly (on the commandline) or implicitly (via a configuration file).
*
* @return array
*/
protected function defaultOptions()
{
return [
FormatterOptions::FIELDS => [],
FormatterOptions::FIELD_LABELS => [],
];
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Formatters\FormatterAwareInterface;
interface RenderCellCollectionInterface extends RenderCellInterface, FormatterAwareInterface
{
const PRIORITY_FIRST = 'first';
const PRIORITY_NORMAL = 'normal';
const PRIORITY_FALLBACK = 'fallback';
/**
* Add a renderer
*
* @return $this
*/
public function addRenderer(RenderCellInterface $renderer);
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
/**
* A RowsOfFields data structure that also contains metadata.
* @see MetadataHolderTrait
*/
class RowsOfFieldsWithMetadata extends RowsOfFields implements MetadataInterface, MetadataHolderInterface
{
use MetadataHolderTrait;
public function __constructor($data)
{
parent::__construct($data);
}
/**
* Restructure this data for output by converting it into a table
* transformation object. First, though, remove any metadata items.
*
* @param FormatterOptions $options Options that affect output formatting.
* @return Consolidation\OutputFormatters\Transformations\TableTransformation
*/
public function restructure(FormatterOptions $options)
{
$originalData = $this->getArrayCopy();
$data = $this->extractData($originalData);
$tableTranformer = $this->createTableTransformation($data, $options);
$tableTranformer->setOriginalData($this);
return $tableTranformer;
}
public function getMetadata()
{
return $this->extractMetadata($this->getArrayCopy());
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Formatters\FormatterAwareInterface;
use Consolidation\OutputFormatters\Formatters\FormatterAwareTrait;
/**
* Create a formatter to add commas to numeric data.
*
* Example:
*
* -------
* Value
* -------
* 2,384
* 143,894
* 23
* 98,538
*
* This formatter may also be re-used for other purposes where right-justified
* data is desired by simply making a subclass. See method comments below.
*
* Usage:
*
* return (new RowsOfFields($data))->addRenderer(
* new NumericCellRenderer($data, ['value'])
* );
*
*/
class NumericCellRenderer implements RenderCellInterface, FormatterAwareInterface
{
use FormatterAwareTrait;
protected $data;
protected $renderedColumns;
protected $widths = [];
/**
* NumericCellRenderer constructor
*/
public function __construct($data, $renderedColumns)
{
$this->data = $data;
$this->renderedColumns = $renderedColumns;
}
/**
* @inheritdoc
*/
public function renderCell($key, $cellData, FormatterOptions $options, $rowData)
{
if (!$this->isRenderedFormat($options) || !$this->isRenderedColumn($key)) {
return $cellData;
}
if ($this->isRenderedData($cellData)) {
$cellData = $this->formatCellData($cellData);
}
return $this->justifyCellData($key, $cellData);
}
/**
* Right-justify the cell data.
*/
protected function justifyCellData($key, $cellData)
{
return str_pad($cellData, $this->columnWidth($key), " ", STR_PAD_LEFT);
}
/**
* Determine if this format is to be formatted.
*/
protected function isRenderedFormat(FormatterOptions $options)
{
return $this->isHumanReadable();
}
/**
* Determine if this is a column that should be formatted.
*/
protected function isRenderedColumn($key)
{
return array_key_exists($key, $this->renderedColumns);
}
/**
* Ignore cell data that should not be formatted.
*/
protected function isRenderedData($cellData)
{
return is_numeric($cellData);
}
/**
* Format the cell data.
*/
protected function formatCellData($cellData)
{
return number_format($this->convertCellDataToString($cellData));
}
/**
* This formatter only works with columns whose columns are strings.
* To use this formatter for another purpose, override this method
* to ensure that the cell data is a string before it is formatted.
*/
protected function convertCellDataToString($cellData)
{
return $cellData;
}
/**
* Get the cached column width for the provided key.
*/
protected function columnWidth($key)
{
if (!isset($this->widths[$key])) {
$this->widths[$key] = $this->calculateColumnWidth($key);
}
return $this->widths[$key];
}
/**
* Using the cached table data, calculate the largest width
* for the data in the table for use when right-justifying.
*/
protected function calculateColumnWidth($key)
{
$width = isset($this->renderedColumns[$key]) ? $this->renderedColumns[$key] : 0;
foreach ($this->data as $row) {
$data = $this->formatCellData($row[$key]);
$width = max(strlen($data), $width);
}
return $width;
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
/**
* Old name for PropertyList class.
*
* @deprecated
*/
class AssociativeList extends PropertyList
{
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
/**
* A structured data object may contains some elements that
* are actually metadata. Metadata is not included in the
* output of tabular data formatters (e.g. table, csv), although
* some of these (e.g. table) may render the metadata alongside
* the data. Raw data formatters (e.g. yaml, json) will render
* both the data and the metadata.
*
* There are two possible options for the data format; either the
* data is nested inside some element, and ever other item is
* metadata, or the metadata may be nested inside some element,
* and every other item is the data rows.
*
* Example 1: nested data
*
* [
* 'data' => [ ... rows of field data ... ],
* 'metadata1' => '...',
* 'metadata2' => '...',
* ]
*
* Example 2: nested metadata
*
* [
* 'metadata' => [ ... metadata items ... ],
* 'rowid1' => [ ... ],
* 'rowid2' => [ ... ],
* ]
*
* It is, of course, also possible that both the data and
* the metadata may be nested inside subelements.
*/
trait MetadataHolderTrait
{
protected $dataKey = false;
protected $metadataKey = false;
public function getDataKey()
{
return $this->dataKey;
}
public function setDataKey($key)
{
$this->dataKey = $key;
return $this;
}
public function getMetadataKey()
{
return $this->metadataKey;
}
public function setMetadataKey($key)
{
$this->metadataKey = $key;
return $this;
}
public function extractData($data)
{
if ($this->metadataKey) {
unset($data[$this->metadataKey]);
}
if ($this->dataKey) {
if (!isset($data[$this->dataKey])) {
return [];
}
return $data[$this->dataKey];
}
return $data;
}
public function extractMetadata($data)
{
if (!$this->dataKey && !$this->metadataKey) {
return [];
}
if ($this->dataKey) {
unset($data[$this->dataKey]);
}
if ($this->metadataKey) {
if (!isset($data[$this->metadataKey])) {
return [];
}
return $data[$this->metadataKey];
}
return $data;
}
public function reconstruct($data, $metadata)
{
$reconstructedData = ($this->dataKey) ? [$this->dataKey => $data] : $data;
$reconstructedMetadata = ($this->metadataKey) ? [$this->metadataKey => $metadata] : $metadata;
return $reconstructedData + $reconstructedMetadata;
}
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface MetadataInterface
{
/**
* Return the metadata associated with the structured data (if any)
*/
public function getMetadata();
}
<?php
namespace Consolidation\OutputFormatters\StructuredData;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
use Consolidation\OutputFormatters\Transformations\UnstructuredDataListTransformation;
/**
* FieldProcessor will do various alterations on field sets.
*/
class FieldProcessor
{
public static function processFieldAliases($fields)
{
if (!is_array($fields)) {
$fields = array_filter(explode(',', $fields));
}
$transformed_fields = [];
foreach ($fields as $field) {
list($machine_name,$label) = explode(' as ', $field) + [$field, preg_replace('#.*\.#', '', $field)];
$transformed_fields[$machine_name] = $label;
}
return $transformed_fields;
}
/**
* Determine whether the data structure has unstructured field access,
* e.g. `a.b.c` or `foo as bar`.
* @param type $fields
* @return type
*/
public static function hasUnstructuredFieldAccess($fields)
{
if (is_array($fields)) {
$fields = implode(',', $fields);
}
return (strpos($fields, ' as ') !== false) || (strpos($fields, '.') !== false);
}
}
<?php
namespace Consolidation\OutputFormatters\Validate;
/**
* Formatters may implement ValidationInterface in order to indicate
* whether a particular data structure is supported. Any formatter that does
* not implement ValidationInterface is assumed to only operate on arrays,
* or data types that implement SimplifyToArrayInterface.
*/
interface ValidationInterface
{
/**
* Return true if the specified format is valid for use with
* this formatter.
*/
public function isValidDataType(\ReflectionClass $dataType);
/**
* Throw an IncompatibleDataException if the provided data cannot
* be processed by this formatter. Return the source data if it
* is valid. The data may be encapsulated or converted if necessary.
*
* @param mixed $structuredData Data to validate
*
* @return mixed
*/
public function validate($structuredData);
}
<?php
namespace Consolidation\OutputFormatters\Validate;
/**
* Provides a default implementation of isValidDataType.
*
* Users of this trait are expected to implement ValidDataTypesInterface.
*/
trait ValidDataTypesTrait
{
/**
* Return the list of data types acceptable to this formatter
*
* @return \ReflectionClass[]
*/
abstract public function validDataTypes();
/**
* Return the list of data types acceptable to this formatter
*/
public function isValidDataType(\ReflectionClass $dataType)
{
return array_reduce(
$this->validDataTypes(),
function ($carry, $supportedType) use ($dataType) {
return
$carry ||
($dataType->getName() == $supportedType->getName()) ||
($dataType->isSubclassOf($supportedType->getName()));
},
false
);
}
}
<?php
namespace Consolidation\OutputFormatters\Validate;
/**
* Formatters may implement ValidDataTypesInterface in order to indicate
* exactly which formats they support. The validDataTypes method can be
* called to retrieve a list of data types useful in providing hints in
* exception messages about which data types can be used with the formatter.
*
* Note that it is OPTIONAL for formatters to implement this interface.
* If a formatter implements only ValidationInterface, then clients that
* request the formatter via FormatterManager::write() will still get a list
* (via an InvalidFormatException) of all of the formats that are usable
* with the provided data type. Implementing ValidDataTypesInterface is
* benefitial to clients who instantiate a formatter directly (via `new`).
*
* Formatters that implement ValidDataTypesInterface may wish to use
* ValidDataTypesTrait.
*/
interface ValidDataTypesInterface extends ValidationInterface
{
/**
* Return the list of data types acceptable to this formatter
*
* @return \ReflectionClass[]
*/
public function validDataTypes();
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Dflydev\DotAccessData\Data;
class UnstructuredDataFieldAccessor
{
protected $data;
public function __construct($data)
{
$this->data = $data;
}
public function get($fields)
{
$data = new Data($this->data);
$result = new Data();
foreach ($fields as $key => $label) {
$item = $data->get($key);
if (isset($item)) {
if ($label == '.') {
if (!is_array($item)) {
return $item;
}
foreach ($item as $key => $value) {
$result->set($key, $value);
}
} else {
$result->set($label, $data->get($key));
}
}
}
return $result->export();
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
/**
* Transform a string of properties into a PHP associative array.
*
* Input:
*
* one: red
* two: white
* three: blue
*
* Output:
*
* [
* 'one' => 'red',
* 'two' => 'white',
* 'three' => 'blue',
* ]
*/
class PropertyParser
{
public static function parse($data)
{
if (!is_string($data)) {
return $data;
}
$result = [];
$lines = explode("\n", $data);
foreach ($lines as $line) {
list($key, $value) = explode(':', trim($line), 2) + ['', ''];
if (!empty($key) && !empty($value)) {
$result[$key] = trim($value);
}
}
return $result;
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations\Wrap;
use Symfony\Component\Console\Helper\TableStyle;
/**
* Calculate column widths for table cells.
*
* Influenced by Drush and webmozart/console.
*/
class CalculateWidths
{
public function __construct()
{
}
/**
* Given the total amount of available space, and the width of
* the columns to place, calculate the optimum column widths to use.
*/
public function calculate($availableWidth, ColumnWidths $dataWidths, ColumnWidths $minimumWidths)
{
// First, check to see if all columns will fit at their full widths.
// If so, do no further calculations. (This may be redundant with
// the short column width calculation.)
if ($dataWidths->totalWidth() <= $availableWidth) {
return $dataWidths->enforceMinimums($minimumWidths);
}
// Get the short columns first. If there are none, then distribute all
// of the available width among the remaining columns.
$shortColWidths = $this->getShortColumns($availableWidth, $dataWidths, $minimumWidths);
if ($shortColWidths->isEmpty()) {
return $this->distributeLongColumns($availableWidth, $dataWidths, $minimumWidths);
}
// If some short columns were removed, then account for the length
// of the removed columns and make a recursive call (since the average
// width may be higher now, if the removed columns were shorter in
// length than the previous average).
$availableWidth -= $shortColWidths->totalWidth();
$remainingWidths = $dataWidths->removeColumns($shortColWidths->keys());
$remainingColWidths = $this->calculate($availableWidth, $remainingWidths, $minimumWidths);
return $shortColWidths->combine($remainingColWidths);
}
/**
* Calculate the longest cell data from any row of each of the cells.
*/
public function calculateLongestCell($rows)
{
return $this->calculateColumnWidths(
$rows,
function ($cell) {
return strlen($cell);
}
);
}
/**
* Calculate the longest word and longest line in the provided data.
*/
public function calculateLongestWord($rows)
{
return $this->calculateColumnWidths(
$rows,
function ($cell) {
return static::longestWordLength($cell);
}
);
}
protected function calculateColumnWidths($rows, callable $fn)
{
$widths = [];
// Examine each row and find the longest line length and longest
// word in each column.
foreach ($rows as $rowkey => $row) {
foreach ($row as $colkey => $cell) {
$value = $fn($cell);
if ((!isset($widths[$colkey]) || ($widths[$colkey] < $value))) {
$widths[$colkey] = $value;
}
}
}
return new ColumnWidths($widths);
}
/**
* Return all of the columns whose longest line length is less than or
* equal to the average width.
*/
public function getShortColumns($availableWidth, ColumnWidths $dataWidths, ColumnWidths $minimumWidths)
{
$averageWidth = $dataWidths->averageWidth($availableWidth);
$shortColWidths = $dataWidths->findShortColumns($averageWidth);
return $shortColWidths->enforceMinimums($minimumWidths);
}
/**
* Distribute the remainig space among the columns that were not
* included in the list of "short" columns.
*/
public function distributeLongColumns($availableWidth, ColumnWidths $dataWidths, ColumnWidths $minimumWidths)
{
// First distribute the remainder without regard to the minimum widths.
$result = $dataWidths->distribute($availableWidth);
// Find columns that are shorter than their minimum width.
$undersized = $result->findUndersizedColumns($minimumWidths);
// Nothing too small? Great, we're done!
if ($undersized->isEmpty()) {
return $result;
}
// Take out the columns that are too small and redistribute the rest.
$availableWidth -= $undersized->totalWidth();
$remaining = $dataWidths->removeColumns($undersized->keys());
$distributeRemaining = $this->distributeLongColumns($availableWidth, $remaining, $minimumWidths);
return $undersized->combine($distributeRemaining);
}
/**
* Return the length of the longest word in the string.
* @param string $str
* @return int
*/
protected static function longestWordLength($str)
{
$words = preg_split('#[ /-]#', $str);
$lengths = array_map(function ($s) {
return strlen($s);
}, $words);
return max($lengths);
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations\Wrap;
use Symfony\Component\Console\Helper\TableStyle;
/**
* Calculate the width of data in table cells in preparation for word wrapping.
*/
class ColumnWidths
{
protected $widths;
public function __construct($widths = [])
{
$this->widths = $widths;
}
public function paddingSpace(
$paddingInEachCell,
$extraPaddingAtEndOfLine = 0,
$extraPaddingAtBeginningOfLine = 0
) {
return ($extraPaddingAtBeginningOfLine + $extraPaddingAtEndOfLine + (count($this->widths) * $paddingInEachCell));
}
/**
* Find all of the columns that are shorter than the specified threshold.
*/
public function findShortColumns($thresholdWidth)
{
$thresholdWidths = array_fill_keys(array_keys($this->widths), $thresholdWidth);
return $this->findColumnsUnderThreshold($thresholdWidths);
}
/**
* Find all of the columns that are shorter than the corresponding minimum widths.
*/
public function findUndersizedColumns($minimumWidths)
{
return $this->findColumnsUnderThreshold($minimumWidths->widths());
}
protected function findColumnsUnderThreshold(array $thresholdWidths)
{
$shortColWidths = [];
foreach ($this->widths as $key => $maxLength) {
if (isset($thresholdWidths[$key]) && ($maxLength <= $thresholdWidths[$key])) {
$shortColWidths[$key] = $maxLength;
}
}
return new ColumnWidths($shortColWidths);
}
/**
* If the widths specified by this object do not fit within the
* provided avaiable width, then reduce them all proportionally.
*/
public function adjustMinimumWidths($availableWidth, $dataCellWidths)
{
$result = $this->selectColumns($dataCellWidths->keys());
if ($result->isEmpty()) {
return $result;
}
$numberOfColumns = $dataCellWidths->count();
// How many unspecified columns are there?
$unspecifiedColumns = $numberOfColumns - $result->count();
$averageWidth = $this->averageWidth($availableWidth);
// Reserve some space for the columns that have no minimum.
// Make sure they collectively get at least half of the average
// width for each column. Or should it be a quarter?
$reservedSpacePerColumn = ($averageWidth / 2);
$reservedSpace = $reservedSpacePerColumn * $unspecifiedColumns;
// Calculate how much of the available space is remaining for use by
// the minimum column widths after the reserved space is accounted for.
$remainingAvailable = $availableWidth - $reservedSpace;
// Don't do anything if our widths fit inside the available widths.
if ($result->totalWidth() <= $remainingAvailable) {
return $result;
}
// Shrink the minimum widths if the table is too compressed.
return $result->distribute($remainingAvailable);
}
/**
* Return proportional weights
*/
public function distribute($availableWidth)
{
$result = [];
$totalWidth = $this->totalWidth();
$lastColumn = $this->lastColumn();
$widths = $this->widths();
// Take off the last column, and calculate proportional weights
// for the first N-1 columns.
array_pop($widths);
foreach ($widths as $key => $width) {
$result[$key] = round(($width / $totalWidth) * $availableWidth);
}
// Give the last column the rest of the available width
$usedWidth = $this->sumWidth($result);
$result[$lastColumn] = $availableWidth - $usedWidth;
return new ColumnWidths($result);
}
public function lastColumn()
{
$keys = $this->keys();
return array_pop($keys);
}
/**
* Return the number of columns.
*/
public function count()
{
return count($this->widths);
}
/**
* Calculate how much space is available on average for all columns.
*/
public function averageWidth($availableWidth)
{
if ($this->isEmpty()) {
debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
}
return $availableWidth / $this->count();
}
/**
* Return the available keys (column identifiers) from the calculated
* data set.
*/
public function keys()
{
return array_keys($this->widths);
}
/**
* Set the length of the specified column.
*/
public function setWidth($key, $width)
{
$this->widths[$key] = $width;
}
/**
* Return the length of the specified column.
*/
public function width($key)
{
return isset($this->widths[$key]) ? $this->widths[$key] : 0;
}
/**
* Return all of the lengths
*/
public function widths()
{
return $this->widths;
}
/**
* Return true if there is no data in this object
*/
public function isEmpty()
{
return empty($this->widths);
}
/**
* Return the sum of the lengths of the provided widths.
*/
public function totalWidth()
{
return static::sumWidth($this->widths());
}
/**
* Return the sum of the lengths of the provided widths.
*/
public static function sumWidth($widths)
{
return array_reduce(
$widths,
function ($carry, $item) {
return $carry + $item;
}
);
}
/**
* Ensure that every item in $widths that has a corresponding entry
* in $minimumWidths is as least as large as the minimum value held there.
*/
public function enforceMinimums($minimumWidths)
{
$result = [];
if ($minimumWidths instanceof ColumnWidths) {
$minimumWidths = $minimumWidths->widths();
}
$minimumWidths += $this->widths;
foreach ($this->widths as $key => $value) {
$result[$key] = max($value, $minimumWidths[$key]);
}
return new ColumnWidths($result);
}
/**
* Remove all of the specified columns from this data structure.
*/
public function removeColumns($columnKeys)
{
$widths = $this->widths();
foreach ($columnKeys as $key) {
unset($widths[$key]);
}
return new ColumnWidths($widths);
}
/**
* Select all columns that exist in the provided list of keys.
*/
public function selectColumns($columnKeys)
{
$widths = [];
foreach ($columnKeys as $key) {
if (isset($this->widths[$key])) {
$widths[$key] = $this->width($key);
}
}
return new ColumnWidths($widths);
}
/**
* Combine this set of widths with another set, and return
* a new set that contains the entries from both.
*/
public function combine(ColumnWidths $combineWith)
{
// Danger: array_merge renumbers numeric keys; that must not happen here.
$combined = $combineWith->widths();
foreach ($this->widths() as $key => $value) {
$combined[$key] = $value;
}
return new ColumnWidths($combined);
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
class PropertyListTableTransformation extends TableTransformation
{
public function getOriginalData()
{
$data = $this->getArrayCopy();
return $data[0];
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
use Consolidation\OutputFormatters\StructuredData\OriginalDataInterface;
use Consolidation\OutputFormatters\StructuredData\MetadataHolderInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Formatters\TsvFormatter;
use Symfony\Component\Console\Output\BufferedOutput;
class TableTransformation extends \ArrayObject implements TableDataInterface, StringTransformationInterface, OriginalDataInterface
{
protected $headers;
protected $rowLabels;
protected $layout;
/** @var MetadataHolderInterface */
protected $originalData;
const TABLE_LAYOUT = 'table';
const LIST_LAYOUT = 'list';
public function __construct($data, $fieldLabels, $rowLabels = [])
{
$this->headers = $fieldLabels;
$this->rowLabels = $rowLabels;
$rows = static::transformRows($data, $fieldLabels);
$this->layout = self::TABLE_LAYOUT;
parent::__construct($rows);
}
public function setLayout($layout)
{
$this->layout = $layout;
}
public function getLayout()
{
return $this->layout;
}
public function isList()
{
return $this->layout == self::LIST_LAYOUT;
}
/**
* @inheritdoc
*/
public function simplifyToString(FormatterOptions $options)
{
$alternateFormatter = new TsvFormatter();
$output = new BufferedOutput();
try {
$data = $alternateFormatter->validate($this->getArrayCopy());
$alternateFormatter->write($output, $this->getArrayCopy(), $options);
} catch (\Exception $e) {
}
return $output->fetch();
}
protected static function transformRows($data, $fieldLabels)
{
$rows = [];
foreach ($data as $rowid => $row) {
$rows[$rowid] = static::transformRow($row, $fieldLabels);
}
return $rows;
}
protected static function transformRow($row, $fieldLabels)
{
$result = [];
foreach ($fieldLabels as $key => $label) {
$result[$key] = array_key_exists($key, $row) ? $row[$key] : '';
}
return $result;
}
public function getHeaders()
{
return $this->headers;
}
public function getHeader($key)
{
if (array_key_exists($key, $this->headers)) {
return $this->headers[$key];
}
return $key;
}
public function getRowLabels()
{
return $this->rowLabels;
}
public function getRowLabel($rowid)
{
if (array_key_exists($rowid, $this->rowLabels)) {
return $this->rowLabels[$rowid];
}
return $rowid;
}
public function getOriginalData()
{
if (isset($this->originalData)) {
return $this->originalData->reconstruct($this->getArrayCopy(), $this->originalData->getMetadata());
}
return $this->getArrayCopy();
}
public function setOriginalData(MetadataHolderInterface $data)
{
$this->originalData = $data;
}
public function getTableData($includeRowKey = false)
{
$data = $this->getArrayCopy();
if ($this->isList()) {
$data = $this->convertTableToList();
}
if ($includeRowKey) {
$data = $this->getRowDataWithKey($data);
}
return $data;
}
protected function convertTableToList()
{
$result = [];
foreach ($this as $row) {
foreach ($row as $key => $value) {
$result[$key][] = $value;
}
}
return $result;
}
protected function getRowDataWithKey($data)
{
$result = [];
$i = 0;
foreach ($data as $key => $row) {
array_unshift($row, $this->getHeader($key));
$i++;
$result[$key] = $row;
}
return $result;
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Consolidation\OutputFormatters\Options\FormatterOptions;
class UnstructuredDataTransformation extends \ArrayObject implements StringTransformationInterface
{
protected $originalData;
public function __construct($data, $fields)
{
$this->originalData = $data;
$rows = static::transformRow($data, $fields);
parent::__construct($rows);
}
public function simplifyToString(FormatterOptions $options)
{
return static::simplifyRow($this->getArrayCopy());
}
public static function transformRow($row, $fields)
{
if (empty($fields)) {
return $row;
}
$fieldAccessor = new UnstructuredDataFieldAccessor($row);
return $fieldAccessor->get($fields);
}
public static function simplifyRow($row)
{
if (is_string($row)) {
return $row;
}
if (static::isSimpleArray($row)) {
return implode("\n", $row);
}
// No good way to simplify - just dump a json fragment
return json_encode($row);
}
protected static function isSimpleArray($row)
{
foreach ($row as $item) {
if (!is_string($item)) {
return false;
}
}
return true;
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Consolidation\OutputFormatters\Options\FormatterOptions;
class UnstructuredDataListTransformation extends \ArrayObject implements StringTransformationInterface
{
public function __construct($data, $fields)
{
$this->originalData = $data;
$rows = static::transformRows($data, $fields);
parent::__construct($rows);
}
protected static function transformRows($data, $fields)
{
$rows = [];
foreach ($data as $rowid => $row) {
$rows[$rowid] = UnstructuredDataTransformation::transformRow($row, $fields);
}
return $rows;
}
public function simplifyToString(FormatterOptions $options)
{
$result = '';
$iterator = $this->getIterator();
while ($iterator->valid()) {
$simplifiedRow = UnstructuredDataTransformation::simplifyRow($iterator->current());
if (isset($simplifiedRow)) {
$result .= "$simplifiedRow\n";
}
$iterator->next();
}
return $result;
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Symfony\Component\Finder\Glob;
use Consolidation\OutputFormatters\Exception\UnknownFieldException;
/**
* Reorder the field labels based on the user-selected fields
* to display.
*/
class ReorderFields
{
/**
* Given a simple list of user-supplied field keys or field labels,
* return a reordered version of the field labels matching the
* user selection.
*
* @param string|array $fields The user-selected fields
* @param array $fieldLabels An associative array mapping the field
* key to the field label
* @param array $data The data that will be rendered.
*
* @return array
*/
public function reorder($fields, $fieldLabels, $data)
{
$firstRow = reset($data);
if (!$firstRow) {
$firstRow = $fieldLabels;
}
if (empty($fieldLabels) && !empty($data)) {
$fieldLabels = array_combine(array_keys($firstRow), array_map('ucfirst', array_keys($firstRow)));
}
$fields = $this->getSelectedFieldKeys($fields, $fieldLabels);
if (empty($fields)) {
return array_intersect_key($fieldLabels, $firstRow);
}
return $this->reorderFieldLabels($fields, $fieldLabels, $data);
}
protected function reorderFieldLabels($fields, $fieldLabels, $data)
{
$result = [];
$firstRow = reset($data);
if (!$firstRow) {
$firstRow = $fieldLabels;
}
foreach ($fields as $field) {
if (array_key_exists($field, $firstRow)) {
if (array_key_exists($field, $fieldLabels)) {
$result[$field] = $fieldLabels[$field];
}
}
}
return $result;
}
protected function getSelectedFieldKeys($fields, $fieldLabels)
{
if (empty($fieldLabels)) {
return [];
}
if (is_string($fields)) {
$fields = explode(',', $fields);
}
$selectedFields = [];
foreach ($fields as $field) {
$matchedFields = $this->matchFieldInLabelMap($field, $fieldLabels);
if (empty($matchedFields)) {
throw new UnknownFieldException($field);
}
$selectedFields = array_merge($selectedFields, $matchedFields);
}
return $selectedFields;
}
protected function matchFieldInLabelMap($field, $fieldLabels)
{
$fieldRegex = $this->convertToRegex($field);
return
array_filter(
array_keys($fieldLabels),
function ($key) use ($fieldRegex, $fieldLabels) {
$value = $fieldLabels[$key];
return preg_match($fieldRegex, $value) || preg_match($fieldRegex, $key);
}
);
}
/**
* Convert the provided string into a regex suitable for use in
* preg_match.
*
* Matching occurs in the same way as the Symfony Finder component:
* http://symfony.com/doc/current/components/finder.html#file-name
*/
protected function convertToRegex($str)
{
return $this->isRegex($str) ? $str : Glob::toRegex($str);
}
/**
* Checks whether the string is a regex. This function is copied from
* MultiplePcreFilterIterator in the Symfony Finder component.
*
* @param string $str
*
* @return bool Whether the given string is a regex
*/
protected function isRegex($str)
{
if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) {
$start = substr($m[1], 0, 1);
$end = substr($m[1], -1);
if ($start === $end) {
return !preg_match('/[*?[:alnum:] \\\\]/', $start);
}
foreach (array(array('{', '}'), array('(', ')'), array('[', ']'), array('<', '>')) as $delimiters) {
if ($start === $delimiters[0] && $end === $delimiters[1]) {
return true;
}
}
}
return false;
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface;
use Consolidation\OutputFormatters\StructuredData\Xml\XmlSchema;
/**
* Simplify a DOMDocument to an array.
*/
class DomToArraySimplifier implements SimplifyToArrayInterface
{
public function __construct()
{
}
/**
* @param ReflectionClass $dataType
*/
public function canSimplify(\ReflectionClass $dataType)
{
return
$dataType->isSubclassOf('\Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface') ||
$dataType->isSubclassOf('DOMDocument') ||
($dataType->getName() == 'DOMDocument');
}
public function simplifyToArray($structuredData, FormatterOptions $options)
{
if ($structuredData instanceof DomDataInterface) {
$structuredData = $structuredData->getDomData();
}
if ($structuredData instanceof \DOMDocument) {
// $schema = $options->getXmlSchema();
$simplified = $this->elementToArray($structuredData);
$structuredData = array_shift($simplified);
}
return $structuredData;
}
/**
* Recursively convert the provided DOM element into a php array.
*
* @param \DOMNode $element
* @return array
*/
protected function elementToArray(\DOMNode $element)
{
if ($element->nodeType == XML_TEXT_NODE) {
return $element->nodeValue;
}
$attributes = $this->getNodeAttributes($element);
$children = $this->getNodeChildren($element);
return array_merge($attributes, $children);
}
/**
* Get all of the attributes of the provided element.
*
* @param \DOMNode $element
* @return array
*/
protected function getNodeAttributes($element)
{
if (empty($element->attributes)) {
return [];
}
$attributes = [];
foreach ($element->attributes as $key => $attribute) {
$attributes[$key] = $attribute->nodeValue;
}
return $attributes;
}
/**
* Get all of the children of the provided element, with simplification.
*
* @param \DOMNode $element
* @return array
*/
protected function getNodeChildren($element)
{
if (empty($element->childNodes)) {
return [];
}
$uniformChildrenName = $this->hasUniformChildren($element);
// Check for plurals.
if (in_array($element->nodeName, ["{$uniformChildrenName}s", "{$uniformChildrenName}es"])) {
$result = $this->getUniformChildren($element->nodeName, $element);
} else {
$result = $this->getUniqueChildren($element->nodeName, $element);
}
return array_filter($result);
}
/**
* Get the data from the children of the provided node in preliminary
* form.
*
* @param \DOMNode $element
* @return array
*/
protected function getNodeChildrenData($element)
{
$children = [];
foreach ($element->childNodes as $key => $value) {
$children[$key] = $this->elementToArray($value);
}
return $children;
}
/**
* Determine whether the children of the provided element are uniform.
* @see getUniformChildren(), below.
*
* @param \DOMNode $element
* @return boolean
*/
protected function hasUniformChildren($element)
{
$last = false;
foreach ($element->childNodes as $key => $value) {
$name = $value->nodeName;
if (!$name) {
return false;
}
if ($last && ($name != $last)) {
return false;
}
$last = $name;
}
return $last;
}
/**
* Convert the children of the provided DOM element into an array.
* Here, 'uniform' means that all of the element names of the children
* are identical, and further, the element name of the parent is the
* plural form of the child names. When the children are uniform in
* this way, then the parent element name will be used as the key to
* store the children in, and the child list will be returned as a
* simple list with their (duplicate) element names omitted.
*
* @param string $parentKey
* @param \DOMNode $element
* @return array
*/
protected function getUniformChildren($parentKey, $element)
{
$children = $this->getNodeChildrenData($element);
$simplifiedChildren = [];
foreach ($children as $key => $value) {
if ($this->valueCanBeSimplified($value)) {
$value = array_shift($value);
}
$id = $this->getIdOfValue($value);
if ($id) {
$simplifiedChildren[$parentKey][$id] = $value;
} else {
$simplifiedChildren[$parentKey][] = $value;
}
}
return $simplifiedChildren;
}
/**
* Determine whether the provided value has additional unnecessary
* nesting. {"color": "red"} is converted to "red". No other
* simplification is done.
*
* @param \DOMNode $value
* @return boolean
*/
protected function valueCanBeSimplified($value)
{
if (!is_array($value)) {
return false;
}
if (count($value) != 1) {
return false;
}
$data = array_shift($value);
return is_string($data);
}
/**
* If the object has an 'id' or 'name' element, then use that
* as the array key when storing this value in its parent.
* @param mixed $value
* @return string
*/
protected function getIdOfValue($value)
{
if (!is_array($value)) {
return false;
}
if (array_key_exists('id', $value)) {
return trim($value['id'], '-');
}
if (array_key_exists('name', $value)) {
return trim($value['name'], '-');
}
}
/**
* Convert the children of the provided DOM element into an array.
* Here, 'unique' means that all of the element names of the children are
* different. Since the element names will become the key of the
* associative array that is returned, so duplicates are not supported.
* If there are any duplicates, then an exception will be thrown.
*
* @param string $parentKey
* @param \DOMNode $element
* @return array
*/
protected function getUniqueChildren($parentKey, $element)
{
$children = $this->getNodeChildrenData($element);
if ((count($children) == 1) && (is_string($children[0]))) {
return [$element->nodeName => $children[0]];
}
$simplifiedChildren = [];
foreach ($children as $key => $value) {
if (is_numeric($key) && is_array($value) && (count($value) == 1)) {
$valueKeys = array_keys($value);
$key = $valueKeys[0];
$value = array_shift($value);
}
if (array_key_exists($key, $simplifiedChildren)) {
throw new \Exception("Cannot convert data from a DOM document to an array, because <$key> appears more than once, and is not wrapped in a <{$key}s> element.");
}
$simplifiedChildren[$key] = $value;
}
return $simplifiedChildren;
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Consolidation\OutputFormatters\Transformations\Wrap\CalculateWidths;
use Consolidation\OutputFormatters\Transformations\Wrap\ColumnWidths;
use Symfony\Component\Console\Helper\TableStyle;
class WordWrapper
{
protected $width;
protected $minimumWidths;
// For now, hardcode these to match what the Symfony Table helper does.
// Note that these might actually need to be adjusted depending on the
// table style.
protected $extraPaddingAtBeginningOfLine = 0;
protected $extraPaddingAtEndOfLine = 0;
protected $paddingInEachCell = 3;
public function __construct($width)
{
$this->width = $width;
$this->minimumWidths = new ColumnWidths();
}
/**
* Calculate our padding widths from the specified table style.
* @param TableStyle $style
*/
public function setPaddingFromStyle(TableStyle $style)
{
$verticalBorderLen = strlen(sprintf($style->getBorderFormat(), $style->getVerticalBorderChar()));
$paddingLen = strlen($style->getPaddingChar());
$this->extraPaddingAtBeginningOfLine = 0;
$this->extraPaddingAtEndOfLine = $verticalBorderLen;
$this->paddingInEachCell = $verticalBorderLen + $paddingLen + 1;
}
/**
* If columns have minimum widths, then set them here.
* @param array $minimumWidths
*/
public function setMinimumWidths($minimumWidths)
{
$this->minimumWidths = new ColumnWidths($minimumWidths);
}
/**
* Set the minimum width of just one column
*/
public function minimumWidth($colkey, $width)
{
$this->minimumWidths->setWidth($colkey, $width);
}
/**
* Wrap the cells in each part of the provided data table
* @param array $rows
* @return array
*/
public function wrap($rows, $widths = [])
{
$auto_widths = $this->calculateWidths($rows, $widths);
// If no widths were provided, then disable wrapping
if ($auto_widths->isEmpty()) {
return $rows;
}
// Do wordwrap on all cells.
$newrows = array();
foreach ($rows as $rowkey => $row) {
foreach ($row as $colkey => $cell) {
$newrows[$rowkey][$colkey] = $this->wrapCell($cell, $auto_widths->width($colkey));
}
}
return $newrows;
}
/**
* Determine what widths we'll use for wrapping.
*/
protected function calculateWidths($rows, $widths = [])
{
// Widths must be provided in some form or another, or we won't wrap.
if (empty($widths) && !$this->width) {
return new ColumnWidths();
}
// Technically, `$widths`, if provided here, should be used
// as the exact widths to wrap to. For now we'll just treat
// these as minimum widths
$minimumWidths = $this->minimumWidths->combine(new ColumnWidths($widths));
$calculator = new CalculateWidths();
$dataCellWidths = $calculator->calculateLongestCell($rows);
$availableWidth = $this->width - $dataCellWidths->paddingSpace($this->paddingInEachCell, $this->extraPaddingAtEndOfLine, $this->extraPaddingAtBeginningOfLine);
$this->minimumWidths->adjustMinimumWidths($availableWidth, $dataCellWidths);
return $calculator->calculate($availableWidth, $dataCellWidths, $minimumWidths);
}
/**
* Wrap one cell. Guard against modifying non-strings and
* then call through to wordwrap().
*
* @param mixed $cell
* @param string $cellWidth
* @return mixed
*/
protected function wrapCell($cell, $cellWidth)
{
if (!is_string($cell)) {
return $cell;
}
return wordwrap($cell, $cellWidth, "\n", true);
}
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface StringTransformationInterface
{
/**
* simplifyToString is called by the string formatter to convert
* structured data to a simple string.
*/
public function simplifyToString(FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface OverrideRestructureInterface
{
/**
* Select data to use directly from the structured output,
* before the restructure operation has been executed.
*
* @param mixed $structuredOutput Data to restructure
* @param FormatterOptions $options Formatting options
* @return mixed
*/
public function overrideRestructure($structuredOutput, FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\Transformations;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface SimplifyToArrayInterface
{
/**
* Convert structured data into a generic array, usable by generic
* array-based formatters. Objects that implement this interface may
* be attached to the FormatterManager, and will be used on any data
* structure that needs to be simplified into an array. An array
* simplifier should take no action other than to return its input data
* if it cannot simplify the provided data into an array.
*
* @param mixed $structuredOutput The data to simplify to an array.
*
* @return array
*/
public function simplifyToArray($structuredOutput, FormatterOptions $options);
/**
* Indicate whether or not the given data type can be simplified to an array
*/
public function canSimplify(\ReflectionClass $structuredOutput);
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\ListDataInterface;
use Consolidation\OutputFormatters\StructuredData\RenderCellInterface;
use Consolidation\OutputFormatters\Transformations\OverrideRestructureInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Display the data in a simple list.
*
* This formatter prints a plain, unadorned list of data,
* with each data item appearing on a separate line. If you
* wish your list to contain headers, then use the table
* formatter, and wrap your data in an PropertyList.
*/
class ListFormatter implements FormatterInterface, OverrideRestructureInterface, RenderDataInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$output->writeln(implode("\n", $data));
}
/**
* @inheritdoc
*/
public function overrideRestructure($structuredOutput, FormatterOptions $options)
{
// If the structured data implements ListDataInterface,
// then we will render whatever data its 'getListData'
// method provides.
if ($structuredOutput instanceof ListDataInterface) {
return $this->renderData($structuredOutput, $structuredOutput->getListData($options), $options);
}
}
/**
* @inheritdoc
*/
public function renderData($originalData, $restructuredData, FormatterOptions $options)
{
if ($originalData instanceof RenderCellInterface) {
return $this->renderEachCell($originalData, $restructuredData, $options);
}
return $restructuredData;
}
protected function renderEachCell($originalData, $restructuredData, FormatterOptions $options)
{
foreach ($restructuredData as $key => $cellData) {
$restructuredData[$key] = $originalData->renderCell($key, $cellData, $options, $restructuredData);
}
return $restructuredData;
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
trait FormatterAwareTrait
{
protected $formatter;
public function setFormatter(FormatterInterface $formatter)
{
$this->formatter = $formatter;
}
public function getFormatter()
{
return $this->formatter;
}
public function isHumanReadable()
{
return $this->formatter && $this->formatter instanceof \Consolidation\OutputFormatters\Formatters\HumanReadableFormat;
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
interface MetadataFormatterInterface
{
/**
* Given some metadata, decide how to display it.
*
* @param OutputInterface output stream to write to
* @param array $metadata associative array containing metadata
* @param FormatterOptions formating options
*/
public function writeMetadata(OutputInterface $output, $metadata, FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
use Consolidation\OutputFormatters\Transformations\ReorderFields;
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
use Consolidation\OutputFormatters\StructuredData\PropertyList;
/**
* Display sections of data.
*
* This formatter takes data in the RowsOfFields data type.
* Each row represents one section; the data in each section
* is rendered in two columns, with the key in the first column
* and the value in the second column.
*/
class SectionsFormatter implements FormatterInterface, ValidDataTypesInterface, RenderDataInterface
{
use ValidDataTypesTrait;
use RenderTableDataTrait;
public function validDataTypes()
{
return
[
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields')
];
}
/**
* @inheritdoc
*/
public function validate($structuredData)
{
// If the provided data was of class RowsOfFields
// or PropertyList, it will be converted into
// a TableTransformation object by the restructure call.
if (!$structuredData instanceof TableDataInterface) {
throw new IncompatibleDataException(
$this,
$structuredData,
$this->validDataTypes()
);
}
return $structuredData;
}
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $tableTransformer, FormatterOptions $options)
{
$table = new Table($output);
$table->setStyle('compact');
foreach ($tableTransformer as $rowid => $row) {
$rowLabel = $tableTransformer->getRowLabel($rowid);
$output->writeln('');
$output->writeln($rowLabel);
$sectionData = new PropertyList($row);
$sectionOptions = new FormatterOptions([], $options->getOptions());
$sectionTableTransformer = $sectionData->restructure($sectionOptions);
$table->setRows($sectionTableTransformer->getTableData(true));
$table->render();
}
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Json formatter
*
* Convert an array or ArrayObject into Json.
*/
class JsonFormatter implements FormatterInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$output->writeln(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
interface FormatterInterface
{
/**
* Given structured data, apply appropriate
* formatting, and return a printable string.
*
* @param OutputInterface output stream to write to
* @param mixed $data Structured data to format
* @param FormatterOptions formating options
*/
public function write(OutputInterface $output, $data, FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
use Consolidation\OutputFormatters\Transformations\TableTransformation;
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Comma-separated value formatters
*
* Display the provided structured data in a comma-separated list. If
* there are multiple records provided, then they will be printed
* one per line. The primary data types accepted are RowsOfFields and
* PropertyList. The later behaves exactly like the former, save for
* the fact that it contains but a single row. This formmatter can also
* accept a PHP array; this is also interpreted as a single-row of data
* with no header.
*/
class CsvFormatter implements FormatterInterface, ValidDataTypesInterface, RenderDataInterface
{
use ValidDataTypesTrait;
use RenderTableDataTrait;
public function validDataTypes()
{
return
[
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields'),
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\PropertyList'),
new \ReflectionClass('\ArrayObject'),
];
}
public function validate($structuredData)
{
// If the provided data was of class RowsOfFields
// or PropertyList, it will be converted into
// a TableTransformation object.
if (!is_array($structuredData) && (!$structuredData instanceof TableTransformation)) {
throw new IncompatibleDataException(
$this,
$structuredData,
$this->validDataTypes()
);
}
// If the data was provided to us as a single array, then
// convert it to a single row.
if (is_array($structuredData) && !empty($structuredData)) {
$firstRow = reset($structuredData);
if (!is_array($firstRow)) {
return [$structuredData];
}
}
return $structuredData;
}
/**
* Return default values for formatter options
* @return array
*/
protected function getDefaultFormatterOptions()
{
return [
FormatterOptions::INCLUDE_FIELD_LABELS => true,
FormatterOptions::DELIMITER => ',',
FormatterOptions::CSV_ENCLOSURE => '"',
FormatterOptions::CSV_ESCAPE_CHAR => "\\",
];
}
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$defaults = $this->getDefaultFormatterOptions();
$includeFieldLabels = $options->get(FormatterOptions::INCLUDE_FIELD_LABELS, $defaults);
if ($includeFieldLabels && ($data instanceof TableTransformation)) {
$headers = $data->getHeaders();
$this->writeOneLine($output, $headers, $options);
}
foreach ($data as $line) {
$this->writeOneLine($output, $line, $options);
}
}
/**
* Writes a single a single line of formatted CSV data to the output stream.
*
* @param OutputInterface $output the output stream to write to.
* @param array $data an array of field data to convert to a CSV string.
* @param FormatterOptions $options the specified options for this formatter.
*/
protected function writeOneLine(OutputInterface $output, $data, $options)
{
$defaults = $this->getDefaultFormatterOptions();
$delimiter = $options->get(FormatterOptions::DELIMITER, $defaults);
$enclosure = $options->get(FormatterOptions::CSV_ENCLOSURE, $defaults);
$escapeChar = $options->get(FormatterOptions::CSV_ESCAPE_CHAR, $defaults);
$output->write($this->csvEscape($data, $delimiter, $enclosure, $escapeChar));
}
/**
* Generates a CSV-escaped string from an array of field data.
*
* @param array $data an array of field data to format as a CSV.
* @param string $delimiter the delimiter to use between fields.
* @param string $enclosure character to use when enclosing complex fields.
* @param string $escapeChar character to use when escaping special characters.
*
* @return string|bool the formatted CSV string, or FALSE if the formatting failed.
*/
protected function csvEscape($data, $delimiter = ',', $enclosure = '"', $escapeChar = "\\")
{
$buffer = fopen('php://temp', 'r+');
if (version_compare(PHP_VERSION, '5.5.4', '>=')) {
fputcsv($buffer, $data, $delimiter, $enclosure, $escapeChar);
} else {
fputcsv($buffer, $data, $delimiter, $enclosure);
}
rewind($buffer);
$csv = fgets($buffer);
fclose($buffer);
return $csv;
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
/**
* Var_dump formatter
*
* Run provided data through Symfony VarDumper component.
*/
class VarDumpFormatter implements FormatterInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$dumper = new CliDumper();
$cloned_data = (new VarCloner())->cloneVar($data);
if ($output instanceof StreamOutput) {
// When stream output is used the dumper is smart enough to
// determine whether or not to apply colors to the dump.
// @see Symfony\Component\VarDumper\Dumper\CliDumper::supportsColors
$dumper->dump($cloned_data, $output->getStream());
} else {
// @todo Use dumper return value to get output once we stop support
// VarDumper v2.
$stream = fopen('php://memory', 'r+b');
$dumper->dump($cloned_data, $stream);
$output->writeln(stream_get_contents($stream, -1, 0));
fclose($stream);
}
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
interface FormatterAwareInterface
{
public function setFormatter(FormatterInterface $formatter);
public function getFormatter();
public function isHumanReadable();
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Serialize formatter
*
* Run provided date thruogh serialize.
*/
class SerializeFormatter implements FormatterInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$output->writeln(serialize($data));
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Options\OverrideOptionsInterface;
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
use Consolidation\OutputFormatters\StructuredData\UnstructuredInterface;
use Consolidation\OutputFormatters\Transformations\SimplifiedFormatterInterface;
use Consolidation\OutputFormatters\Transformations\StringTransformationInterface;
use Consolidation\OutputFormatters\Validate\ValidationInterface;
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
use Symfony\Component\Console\Output\OutputInterface;
/**
* String formatter
*
* This formatter is used as the default action when no
* particular formatter is requested. It will print the
* provided data only if it is a string; if any other
* type is given, then nothing is printed.
*/
class StringFormatter implements FormatterInterface, ValidationInterface, OverrideOptionsInterface
{
/**
* By default, we assume that we can convert any data type to `string`,
* unless it implements UnstructuredInterface, in which case we won't
* allow the `string` format unless the data type also implements
* StringTransformationInterface.
*/
public function isValidDataType(\ReflectionClass $dataType)
{
if ($dataType->implementsInterface('\Consolidation\OutputFormatters\StructuredData\UnstructuredInterface') && !$dataType->implementsInterface('\Consolidation\OutputFormatters\Transformations\StringTransformationInterface')) {
return false;
}
return true;
}
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
if (is_string($data)) {
return $output->writeln($data);
}
return $this->reduceToSigleFieldAndWrite($output, $data, $options);
}
/**
* @inheritdoc
*/
public function overrideOptions($structuredOutput, FormatterOptions $options)
{
$defaultField = $options->get(FormatterOptions::DEFAULT_STRING_FIELD, [], '');
$userFields = $options->get(FormatterOptions::FIELDS, [FormatterOptions::FIELDS => $options->get(FormatterOptions::FIELD)]);
$optionsOverride = $options->override([]);
if (empty($userFields) && !empty($defaultField)) {
$optionsOverride->setOption(FormatterOptions::FIELDS, $defaultField);
}
return $optionsOverride;
}
/**
* If the data provided to a 'string' formatter is a table, then try
* to emit it in a simplified form (by default, TSV).
*
* @param OutputInterface $output
* @param mixed $data
* @param FormatterOptions $options
*/
protected function reduceToSigleFieldAndWrite(OutputInterface $output, $data, FormatterOptions $options)
{
if ($data instanceof StringTransformationInterface) {
$simplified = $data->simplifyToString($options);
return $output->write($simplified);
}
$alternateFormatter = new TsvFormatter();
try {
$data = $alternateFormatter->validate($data);
$alternateFormatter->write($output, $data, $options);
} catch (\Exception $e) {
}
}
/**
* Always validate any data, though. This format will never
* cause an error if it is selected for an incompatible data type; at
* worse, it simply does not print any data.
*/
public function validate($structuredData)
{
return $structuredData;
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\StructuredData\RenderCellInterface;
trait RenderTableDataTrait
{
/**
* @inheritdoc
*/
public function renderData($originalData, $restructuredData, FormatterOptions $options)
{
if ($originalData instanceof RenderCellInterface) {
return $this->renderEachCell($originalData, $restructuredData, $options);
}
return $restructuredData;
}
protected function renderEachCell($originalData, $restructuredData, FormatterOptions $options)
{
foreach ($restructuredData as $id => $row) {
foreach ($row as $key => $cellData) {
$restructuredData[$id][$key] = $originalData->renderCell($key, $cellData, $options, $row);
}
}
return $restructuredData;
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Var_export formatter
*
* Run provided date thruogh var_export.
*/
class VarExportFormatter implements FormatterInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$output->writeln(var_export($data, true));
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Symfony\Component\Yaml\Yaml;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Yaml formatter
*
* Convert an array or ArrayObject into Yaml.
*/
class YamlFormatter implements FormatterInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
// Set Yaml\Dumper's default indentation for nested nodes/collections to
// 2 spaces for consistency with Drupal coding standards.
$indent = 2;
// The level where you switch to inline YAML is set to PHP_INT_MAX to
// ensure this does not occur.
$output->writeln(Yaml::dump($data, PHP_INT_MAX, $indent, false, true));
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Validate\ValidationInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
use Symfony\Component\Console\Output\OutputInterface;
/**
* No output formatter
*
* This formatter never produces any output. It is useful in cases where
* a command should not produce any output by default, but may do so if
* the user explicitly includes a --format option.
*/
class NoOutputFormatter implements FormatterInterface, ValidationInterface
{
/**
* All data types are acceptable.
*/
public function isValidDataType(\ReflectionClass $dataType)
{
return true;
}
/**
* @inheritdoc
*/
public function validate($structuredData)
{
return $structuredData;
}
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableStyle;
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
use Consolidation\OutputFormatters\Transformations\ReorderFields;
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
use Consolidation\OutputFormatters\Transformations\WordWrapper;
use Consolidation\OutputFormatters\Formatters\HumanReadableFormat;
/**
* Display a table of data with the Symfony Table class.
*
* This formatter takes data of either the RowsOfFields or
* PropertyList data type. Tables can be rendered with the
* rows running either vertically (the normal orientation) or
* horizontally. By default, associative lists will be displayed
* as two columns, with the key in the first column and the
* value in the second column.
*/
class TableFormatter implements FormatterInterface, ValidDataTypesInterface, RenderDataInterface, MetadataFormatterInterface, HumanReadableFormat
{
use ValidDataTypesTrait;
use RenderTableDataTrait;
use MetadataFormatterTrait;
protected $fieldLabels;
protected $defaultFields;
public function __construct()
{
}
public function validDataTypes()
{
return
[
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\RowsOfFields'),
new \ReflectionClass('\Consolidation\OutputFormatters\StructuredData\PropertyList')
];
}
/**
* @inheritdoc
*/
public function validate($structuredData)
{
// If the provided data was of class RowsOfFields
// or PropertyList, it will be converted into
// a TableTransformation object by the restructure call.
if (!$structuredData instanceof TableDataInterface) {
throw new IncompatibleDataException(
$this,
$structuredData,
$this->validDataTypes()
);
}
return $structuredData;
}
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $tableTransformer, FormatterOptions $options)
{
$headers = [];
$defaults = [
FormatterOptions::TABLE_STYLE => 'consolidation',
FormatterOptions::INCLUDE_FIELD_LABELS => true,
];
$table = new Table($output);
static::addCustomTableStyles($table);
$table->setStyle($options->get(FormatterOptions::TABLE_STYLE, $defaults));
$isList = $tableTransformer->isList();
$includeHeaders = $options->get(FormatterOptions::INCLUDE_FIELD_LABELS, $defaults);
$listDelimiter = $options->get(FormatterOptions::LIST_DELIMITER, $defaults);
$headers = $tableTransformer->getHeaders();
$data = $tableTransformer->getTableData($includeHeaders && $isList);
if ($listDelimiter) {
if (!empty($headers)) {
array_splice($headers, 1, 0, ':');
}
$data = array_map(function ($item) {
array_splice($item, 1, 0, ':');
return $item;
}, $data);
}
if ($includeHeaders && !$isList) {
$table->setHeaders($headers);
}
// todo: $output->getFormatter();
$data = $this->wrap($headers, $data, $table->getStyle(), $options);
$table->setRows($data);
$table->render();
}
/**
* Wrap the table data
* @param array $data
* @param TableStyle $tableStyle
* @param FormatterOptions $options
* @return array
*/
protected function wrap($headers, $data, TableStyle $tableStyle, FormatterOptions $options)
{
$wrapper = new WordWrapper($options->get(FormatterOptions::TERMINAL_WIDTH));
$wrapper->setPaddingFromStyle($tableStyle);
if (!empty($headers)) {
$headerLengths = array_map(function ($item) {
return strlen($item);
}, $headers);
$wrapper->setMinimumWidths($headerLengths);
}
return $wrapper->wrap($data);
}
/**
* Add our custom table style(s) to the table.
*/
protected static function addCustomTableStyles($table)
{
// The 'consolidation' style is the same as the 'symfony-style-guide'
// style, except it maintains the colored headers used in 'default'.
$consolidationStyle = new TableStyle();
$consolidationStyle
->setHorizontalBorderChar('-')
->setVerticalBorderChar(' ')
->setCrossingChar(' ')
;
$table->setStyleDefinition('consolidation', $consolidationStyle);
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
use Consolidation\OutputFormatters\StructuredData\MetadataInterface;
trait MetadataFormatterTrait
{
/**
* @inheritdoc
*/
public function writeMetadata(OutputInterface $output, $structuredOutput, FormatterOptions $options)
{
$template = $options->get(FormatterOptions::METADATA_TEMPLATE);
if (!$template) {
return;
}
if (!$structuredOutput instanceof MetadataInterface) {
return;
}
$metadata = $structuredOutput->getMetadata();
if (empty($metadata)) {
return;
}
$message = $this->interpolate($template, $metadata);
return $output->writeln($message);
}
/**
* Interpolates context values into the message placeholders.
*
* @author PHP Framework Interoperability Group
*
* @param string $message
* @param array $context
*
* @return string
*/
private function interpolate($message, array $context)
{
// build a replacement array with braces around the context keys
$replace = array();
foreach ($context as $key => $val) {
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
$replace[sprintf('{%s}', $key)] = $val;
}
}
// interpolate replacement values into the message and return
return strtr($message, $replace);
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableStyle;
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Validate\ValidDataTypesTrait;
use Consolidation\OutputFormatters\StructuredData\TableDataInterface;
use Consolidation\OutputFormatters\Transformations\ReorderFields;
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
use Consolidation\OutputFormatters\StructuredData\Xml\DomDataInterface;
/**
* Display a table of data with the Symfony Table class.
*
* This formatter takes data of either the RowsOfFields or
* PropertyList data type. Tables can be rendered with the
* rows running either vertically (the normal orientation) or
* horizontally. By default, associative lists will be displayed
* as two columns, with the key in the first column and the
* value in the second column.
*/
class XmlFormatter implements FormatterInterface, ValidDataTypesInterface
{
use ValidDataTypesTrait;
public function __construct()
{
}
public function validDataTypes()
{
return
[
new \ReflectionClass('\DOMDocument'),
new \ReflectionClass('\ArrayObject'),
];
}
/**
* @inheritdoc
*/
public function validate($structuredData)
{
if ($structuredData instanceof \DOMDocument) {
return $structuredData;
}
if ($structuredData instanceof DomDataInterface) {
return $structuredData->getDomData();
}
if ($structuredData instanceof \ArrayObject) {
return $structuredData->getArrayCopy();
}
if (!is_array($structuredData)) {
throw new IncompatibleDataException(
$this,
$structuredData,
$this->validDataTypes()
);
}
return $structuredData;
}
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $dom, FormatterOptions $options)
{
if (is_array($dom)) {
$schema = $options->getXmlSchema();
$dom = $schema->arrayToXML($dom);
}
$dom->formatOutput = true;
$output->writeln($dom->saveXML());
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Print_r formatter
*
* Run provided date thruogh print_r.
*/
class PrintRFormatter implements FormatterInterface
{
/**
* @inheritdoc
*/
public function write(OutputInterface $output, $data, FormatterOptions $options)
{
$output->writeln(print_r($data, true));
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Options\FormatterOptions;
interface RenderDataInterface
{
/**
* Convert the contents of the output data just before it
* is to be printed, prior to output but after restructuring
* and validation.
*
* @param mixed $originalData
* @param mixed $restructuredData
* @param FormatterOptions $options Formatting options
* @return mixed
*/
public function renderData($originalData, $restructuredData, FormatterOptions $options);
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
use Consolidation\OutputFormatters\Validate\ValidDataTypesInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Transformations\TableTransformation;
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Tab-separated value formatters
*
* Display the provided structured data in a tab-separated list. Output
* escaping is much lighter, since there is no allowance for altering
* the delimiter.
*/
class TsvFormatter extends CsvFormatter
{
protected function getDefaultFormatterOptions()
{
return [
FormatterOptions::INCLUDE_FIELD_LABELS => false,
];
}
protected function writeOneLine(OutputInterface $output, $data, $options)
{
$output->writeln($this->tsvEscape($data));
}
protected function tsvEscape($data)
{
return implode("\t", array_map(
function ($item) {
return str_replace(["\t", "\n"], ['\t', '\n'], $item);
},
$data
));
}
}
<?php
namespace Consolidation\OutputFormatters\Formatters;
/**
* Marker interface that indicates that a cell data renderer
* (@see Consolidation\OutputFormatters\SturcturedData\RenderCellInterface)
* may test for to determine whether it is allowable to add
* human-readable formatting into the cell data
* (@see Consolidation\OutputFormatters\SturcturedData\NumericCallRenderer).
*/
interface HumanReadableFormat
{
}
<?php
namespace Consolidation\OutputFormatters;
use Consolidation\OutputFormatters\Exception\IncompatibleDataException;
use Consolidation\OutputFormatters\Exception\InvalidFormatException;
use Consolidation\OutputFormatters\Exception\UnknownFormatException;
use Consolidation\OutputFormatters\Formatters\FormatterAwareInterface;
use Consolidation\OutputFormatters\Formatters\FormatterInterface;
use Consolidation\OutputFormatters\Formatters\MetadataFormatterInterface;
use Consolidation\OutputFormatters\Formatters\RenderDataInterface;
use Consolidation\OutputFormatters\Options\FormatterOptions;
use Consolidation\OutputFormatters\Options\OverrideOptionsInterface;
use Consolidation\OutputFormatters\StructuredData\MetadataInterface;
use Consolidation\OutputFormatters\StructuredData\RestructureInterface;
use Consolidation\OutputFormatters\Transformations\DomToArraySimplifier;
use Consolidation\OutputFormatters\Transformations\OverrideRestructureInterface;
use Consolidation\OutputFormatters\Transformations\SimplifyToArrayInterface;
use Consolidation\OutputFormatters\Validate\ValidationInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Consolidation\OutputFormatters\StructuredData\OriginalDataInterface;
use Consolidation\OutputFormatters\StructuredData\ListDataFromKeys;
use Consolidation\OutputFormatters\StructuredData\ConversionInterface;
use Consolidation\OutputFormatters\Formatters\HumanReadableFormat;
/**
* Manage a collection of formatters; return one on request.
*/
class FormatterManager
{
/** var FormatterInterface[] */
protected $formatters = [];
/** var SimplifyToArrayInterface[] */
protected $arraySimplifiers = [];
public function __construct()
{
}
public function addDefaultFormatters()
{
$defaultFormatters = [
'null' => '\Consolidation\OutputFormatters\Formatters\NoOutputFormatter',
'string' => '\Consolidation\OutputFormatters\Formatters\StringFormatter',
'yaml' => '\Consolidation\OutputFormatters\Formatters\YamlFormatter',
'xml' => '\Consolidation\OutputFormatters\Formatters\XmlFormatter',
'json' => '\Consolidation\OutputFormatters\Formatters\JsonFormatter',
'print-r' => '\Consolidation\OutputFormatters\Formatters\PrintRFormatter',
'php' => '\Consolidation\OutputFormatters\Formatters\SerializeFormatter',
'var_export' => '\Consolidation\OutputFormatters\Formatters\VarExportFormatter',
'list' => '\Consolidation\OutputFormatters\Formatters\ListFormatter',
'csv' => '\Consolidation\OutputFormatters\Formatters\CsvFormatter',
'tsv' => '\Consolidation\OutputFormatters\Formatters\TsvFormatter',
'table' => '\Consolidation\OutputFormatters\Formatters\TableFormatter',
'sections' => '\Consolidation\OutputFormatters\Formatters\SectionsFormatter',
];
if (class_exists('Symfony\Component\VarDumper\Dumper\CliDumper')) {
$defaultFormatters['var_dump'] = '\Consolidation\OutputFormatters\Formatters\VarDumpFormatter';
}
foreach ($defaultFormatters as $id => $formatterClassname) {
$formatter = new $formatterClassname;
$this->addFormatter($id, $formatter);
}
$this->addFormatter('', $this->formatters['string']);
}
public function addDefaultSimplifiers()
{
// Add our default array simplifier (DOMDocument to array)
$this->addSimplifier(new DomToArraySimplifier());
}
/**
* Add a formatter
*
* @param string $key the identifier of the formatter to add
* @param string $formatter the class name of the formatter to add
* @return FormatterManager
*/
public function addFormatter($key, FormatterInterface $formatter)
{
$this->formatters[$key] = $formatter;
return $this;
}
/**
* Add a simplifier
*
* @param SimplifyToArrayInterface $simplifier the array simplifier to add
* @return FormatterManager
*/
public function addSimplifier(SimplifyToArrayInterface $simplifier)
{
$this->arraySimplifiers[] = $simplifier;
return $this;
}
/**
* Return a set of InputOption based on the annotations of a command.
* @param FormatterOptions $options
* @return InputOption[]
*/
public function automaticOptions(FormatterOptions $options, $dataType)
{
$automaticOptions = [];
// At the moment, we only support automatic options for --format
// and --fields, so exit if the command returns no data.
if (!isset($dataType)) {
return [];
}
$validFormats = $this->validFormats($dataType);
if (empty($validFormats)) {
return [];
}
$availableFields = $options->get(FormatterOptions::FIELD_LABELS);
$hasDefaultStringField = $options->get(FormatterOptions::DEFAULT_STRING_FIELD);
$defaultFormat = $hasDefaultStringField ? 'string' : ($availableFields ? 'table' : 'yaml');
if (count($validFormats) > 1) {
// Make an input option for --format
$description = 'Format the result data. Available formats: ' . implode(',', $validFormats);
$automaticOptions[FormatterOptions::FORMAT] = new InputOption(FormatterOptions::FORMAT, '', InputOption::VALUE_REQUIRED, $description, $defaultFormat);
}
$dataTypeClass = ($dataType instanceof \ReflectionClass) ? $dataType : new \ReflectionClass($dataType);
if ($availableFields) {
$defaultFields = $options->get(FormatterOptions::DEFAULT_FIELDS, [], '');
$description = 'Available fields: ' . implode(', ', $this->availableFieldsList($availableFields));
$automaticOptions[FormatterOptions::FIELDS] = new InputOption(FormatterOptions::FIELDS, '', InputOption::VALUE_REQUIRED, $description, $defaultFields);
} elseif ($dataTypeClass->implementsInterface('Consolidation\OutputFormatters\StructuredData\RestructureInterface')) {
$automaticOptions[FormatterOptions::FIELDS] = new InputOption(FormatterOptions::FIELDS, '', InputOption::VALUE_REQUIRED, 'Limit output to only the listed elements. Name top-level elements by key, e.g. "--fields=name,date", or use dot notation to select a nested element, e.g. "--fields=a.b.c as example".', []);
}
if (isset($automaticOptions[FormatterOptions::FIELDS])) {
$automaticOptions[FormatterOptions::FIELD] = new InputOption(FormatterOptions::FIELD, '', InputOption::VALUE_REQUIRED, "Select just one field, and force format to 'string'.", '');
}
return $automaticOptions;
}
/**
* Given a list of available fields, return a list of field descriptions.
* @return string[]
*/
protected function availableFieldsList($availableFields)
{
return array_map(
function ($key) use ($availableFields) {
return $availableFields[$key] . " ($key)";
},
array_keys($availableFields)
);
}
/**
* Return the identifiers for all valid data types that have been registered.
*
* @param mixed $dataType \ReflectionObject or other description of the produced data type
* @return array
*/
public function validFormats($dataType)
{
$validFormats = [];
foreach ($this->formatters as $formatId => $formatterName) {
$formatter = $this->getFormatter($formatId);
if (!empty($formatId) && $this->isValidFormat($formatter, $dataType)) {
$validFormats[] = $formatId;
}
}
sort($validFormats);
return $validFormats;
}
public function isValidFormat(FormatterInterface $formatter, $dataType)
{
if (is_array($dataType)) {
$dataType = new \ReflectionClass('\ArrayObject');
}
if (!is_object($dataType) && !class_exists($dataType)) {
return false;
}
if (!$dataType instanceof \ReflectionClass) {
$dataType = new \ReflectionClass($dataType);
}
return $this->isValidDataType($formatter, $dataType);
}
public function isValidDataType(FormatterInterface $formatter, \ReflectionClass $dataType)
{
if ($this->canSimplifyToArray($dataType)) {
if ($this->isValidFormat($formatter, [])) {
return true;
}
}
// If the formatter does not implement ValidationInterface, then
// it is presumed that the formatter only accepts arrays.
if (!$formatter instanceof ValidationInterface) {
return $dataType->isSubclassOf('ArrayObject') || ($dataType->getName() == 'ArrayObject');
}
return $formatter->isValidDataType($dataType);
}
/**
* Format and write output
*
* @param OutputInterface $output Output stream to write to
* @param string $format Data format to output in
* @param mixed $structuredOutput Data to output
* @param FormatterOptions $options Formatting options
*/
public function write(OutputInterface $output, $format, $structuredOutput, FormatterOptions $options)
{
// Convert the data to another format (e.g. converting from RowsOfFields to
// UnstructuredListData when the fields indicate an unstructured transformation
// is requested).
$structuredOutput = $this->convertData($structuredOutput, $options);
// TODO: If the $format is the default format (not selected by the user), and
// if `convertData` switched us to unstructured data, then select a new default
// format (e.g. yaml) if the selected format cannot render the converted data.
$formatter = $this->getFormatter((string)$format);
// If the data format is not applicable for the selected formatter, throw an error.
if (!is_string($structuredOutput) && !$this->isValidFormat($formatter, $structuredOutput)) {
$validFormats = $this->validFormats($structuredOutput);
throw new InvalidFormatException((string)$format, $structuredOutput, $validFormats);
}
if ($structuredOutput instanceof FormatterAwareInterface) {
$structuredOutput->setFormatter($formatter);
}
// Give the formatter a chance to override the options
$options = $this->overrideOptions($formatter, $structuredOutput, $options);
$restructuredOutput = $this->validateAndRestructure($formatter, $structuredOutput, $options);
if ($formatter instanceof MetadataFormatterInterface) {
$formatter->writeMetadata($output, $structuredOutput, $options);
}
$formatter->write($output, $restructuredOutput, $options);
}
protected function validateAndRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
{
// Give the formatter a chance to do something with the
// raw data before it is restructured.
$overrideRestructure = $this->overrideRestructure($formatter, $structuredOutput, $options);
if ($overrideRestructure) {
return $overrideRestructure;
}
// Restructure the output data (e.g. select fields to display, etc.).
$restructuredOutput = $this->restructureData($structuredOutput, $options);
// Make sure that the provided data is in the correct format for the selected formatter.
$restructuredOutput = $this->validateData($formatter, $restructuredOutput, $options);
// Give the original data a chance to re-render the structured
// output after it has been restructured and validated.
$restructuredOutput = $this->renderData($formatter, $structuredOutput, $restructuredOutput, $options);
return $restructuredOutput;
}
/**
* Fetch the requested formatter.
*
* @param string $format Identifier for requested formatter
* @return FormatterInterface
*/
public function getFormatter($format)
{
// The client must inject at least one formatter before asking for
// any formatters; if not, we will provide all of the usual defaults
// as a convenience.
if (empty($this->formatters)) {
$this->addDefaultFormatters();
$this->addDefaultSimplifiers();
}
if (!$this->hasFormatter($format)) {
throw new UnknownFormatException($format);
}
$formatter = $this->formatters[$format];
return $formatter;
}
/**
* Test to see if the stipulated format exists
*/
public function hasFormatter($format)
{
return array_key_exists($format, $this->formatters);
}
/**
* Render the data as necessary (e.g. to select or reorder fields).
*
* @param FormatterInterface $formatter
* @param mixed $originalData
* @param mixed $restructuredData
* @param FormatterOptions $options Formatting options
* @return mixed
*/
public function renderData(FormatterInterface $formatter, $originalData, $restructuredData, FormatterOptions $options)
{
if ($formatter instanceof RenderDataInterface) {
return $formatter->renderData($originalData, $restructuredData, $options);
}
return $restructuredData;
}
/**
* Determine if the provided data is compatible with the formatter being used.
*
* @param FormatterInterface $formatter Formatter being used
* @param mixed $structuredOutput Data to validate
* @return mixed
*/
public function validateData(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
{
// If the formatter implements ValidationInterface, then let it
// test the data and throw or return an error
if ($formatter instanceof ValidationInterface) {
return $formatter->validate($structuredOutput);
}
// If the formatter does not implement ValidationInterface, then
// it will never be passed an ArrayObject; we will always give
// it a simple array.
$structuredOutput = $this->simplifyToArray($structuredOutput, $options);
// If we could not simplify to an array, then throw an exception.
// We will never give a formatter anything other than an array
// unless it validates that it can accept the data type.
if (!is_array($structuredOutput)) {
throw new IncompatibleDataException(
$formatter,
$structuredOutput,
[]
);
}
return $structuredOutput;
}
protected function simplifyToArray($structuredOutput, FormatterOptions $options)
{
// We can do nothing unless the provided data is an object.
if (!is_object($structuredOutput)) {
return $structuredOutput;
}
// Check to see if any of the simplifiers can convert the given data
// set to an array.
$outputDataType = new \ReflectionClass($structuredOutput);
foreach ($this->arraySimplifiers as $simplifier) {
if ($simplifier->canSimplify($outputDataType)) {
$structuredOutput = $simplifier->simplifyToArray($structuredOutput, $options);
}
}
// Convert data structure back into its original form, if necessary.
if ($structuredOutput instanceof OriginalDataInterface) {
return $structuredOutput->getOriginalData();
}
// Convert \ArrayObjects to a simple array.
if ($structuredOutput instanceof \ArrayObject) {
return $structuredOutput->getArrayCopy();
}
return $structuredOutput;
}
protected function canSimplifyToArray(\ReflectionClass $structuredOutput)
{
foreach ($this->arraySimplifiers as $simplifier) {
if ($simplifier->canSimplify($structuredOutput)) {
return true;
}
}
return false;
}
/**
* Convert from one format to another if necessary prior to restructuring.
*/
public function convertData($structuredOutput, FormatterOptions $options)
{
if ($structuredOutput instanceof ConversionInterface) {
return $structuredOutput->convert($options);
}
return $structuredOutput;
}
/**
* Restructure the data as necessary (e.g. to select or reorder fields).
*
* @param mixed $structuredOutput
* @param FormatterOptions $options
* @return mixed
*/
public function restructureData($structuredOutput, FormatterOptions $options)
{
if ($structuredOutput instanceof RestructureInterface) {
return $structuredOutput->restructure($options);
}
return $structuredOutput;
}
/**
* Allow the formatter access to the raw structured data prior
* to restructuring. For example, the 'list' formatter may wish
* to display the row keys when provided table output. If this
* function returns a result that does not evaluate to 'false',
* then that result will be used as-is, and restructuring and
* validation will not occur.
*
* @param mixed $structuredOutput
* @param FormatterOptions $options
* @return mixed
*/
public function overrideRestructure(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
{
if ($formatter instanceof OverrideRestructureInterface) {
return $formatter->overrideRestructure($structuredOutput, $options);
}
}
/**
* Allow the formatter to mess with the configuration options before any
* transformations et. al. get underway.
* @param FormatterInterface $formatter
* @param mixed $structuredOutput
* @param FormatterOptions $options
* @return FormatterOptions
*/
public function overrideOptions(FormatterInterface $formatter, $structuredOutput, FormatterOptions $options)
{
// Set the "Human Readable" option if the formatter has the HumanReadable marker interface
if ($formatter instanceof HumanReadableFormat) {
$options->setHumanReadable();
}
// The formatter may also make dynamic adjustment to the options.
if ($formatter instanceof OverrideOptionsInterface) {
return $formatter->overrideOptions($structuredOutput, $options);
}
return $options;
}
}
<?php
namespace Consolidation\OutputFormatters\Exception;
/**
* Indicates that the requested format does not exist.
*/
class UnknownFormatException extends \Exception
{
public function __construct($format)
{
$message = "The requested format, '$format', is not available.";
parent::__construct($message, 1);
}
}
<?php
namespace Consolidation\OutputFormatters\Exception;
/**
* Contains some helper functions used by exceptions in this project.
*/
abstract class AbstractDataFormatException extends \Exception
{
/**
* Return a description of the data type represented by the provided parameter.
*
* @param \ReflectionClass $data The data type to describe. Note that
* \ArrayObject is used as a proxy to mean an array primitive (or an ArrayObject).
* @return string
*/
protected static function describeDataType($data)
{
if (is_array($data) || ($data instanceof \ReflectionClass)) {
if (is_array($data) || ($data->getName() == 'ArrayObject')) {
return 'an array';
}
return 'an instance of ' . $data->getName();
}
if (is_string($data)) {
return 'a string';
}
if (is_object($data)) {
return 'an instance of ' . get_class($data);
}
throw new \Exception("Undescribable data error: " . var_export($data, true));
}
protected static function describeAllowedTypes($allowedTypes)
{
if (is_array($allowedTypes) && !empty($allowedTypes)) {
if (count($allowedTypes) > 1) {
return static::describeListOfAllowedTypes($allowedTypes);
}
$allowedTypes = $allowedTypes[0];
}
return static::describeDataType($allowedTypes);
}
protected static function describeListOfAllowedTypes($allowedTypes)
{
$descriptions = [];
foreach ($allowedTypes as $oneAllowedType) {
$descriptions[] = static::describeDataType($oneAllowedType);
}
if (count($descriptions) == 2) {
return "either {$descriptions[0]} or {$descriptions[1]}";
}
$lastDescription = array_pop($descriptions);
$otherDescriptions = implode(', ', $descriptions);
return "one of $otherDescriptions or $lastDescription";
}
}
<?php
namespace Consolidation\OutputFormatters\Exception;
use Consolidation\OutputFormatters\Formatters\FormatterInterface;
/**
* Represents an incompatibility between the output data and selected formatter.
*/
class IncompatibleDataException extends AbstractDataFormatException
{
public function __construct(FormatterInterface $formatter, $data, $allowedTypes)
{
$formatterDescription = get_class($formatter);
$dataDescription = static::describeDataType($data);
$allowedTypesDescription = static::describeAllowedTypes($allowedTypes);
$message = "Data provided to $formatterDescription must be $allowedTypesDescription. Instead, $dataDescription was provided.";
parent::__construct($message, 1);
}
}
<?php
namespace Consolidation\OutputFormatters\Exception;
/**
* Represents an incompatibility between the output data and selected formatter.
*/
class InvalidFormatException extends AbstractDataFormatException
{
public function __construct($format, $data, $validFormats)
{
$dataDescription = static::describeDataType($data);
$message = "The format $format cannot be used with the data produced by this command, which was $dataDescription. Valid formats are: " . implode(',', $validFormats);
parent::__construct($message, 1);
}
}
<?php
namespace Consolidation\OutputFormatters\Exception;
/**
* Indicates that the requested format does not exist.
*/
class UnknownFieldException extends \Exception
{
public function __construct($field)
{
$message = "The requested field, '$field', is not defined.";
parent::__construct($message, 1);
}
}
<?php
namespace SelfUpdate;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem as sfFilesystem;
/**
* Update the robo.phar from the latest github release
*
* @author Alexander Menk <alex.menk@gmail.com>
*/
class SelfUpdateCommand extends Command
{
const SELF_UPDATE_COMMAND_NAME = 'self:update';
protected $gitHubRepository;
protected $currentVersion;
protected $applicationName;
public function __construct($applicationName = null, $currentVersion = null, $gitHubRepository = null)
{
$this->applicationName = $applicationName;
$this->currentVersion = $currentVersion;
$this->gitHubRepository = $gitHubRepository;
parent::__construct(self::SELF_UPDATE_COMMAND_NAME);
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$app = $this->applicationName;
$this
->setAliases(array('update'))
->setDescription("Updates $app to the latest version.")
->setHelp(
<<<EOT
The <info>self-update</info> command checks github for newer
versions of $app and if found, installs the latest.
EOT
);
}
protected function getLatestReleaseFromGithub()
{
$opts = [
'http' => [
'method' => 'GET',
'header' => [
'User-Agent: ' . $this->applicationName . ' (' . $this->gitHubRepository . ')' . ' Self-Update (PHP)'
]
]
];
$context = stream_context_create($opts);
$releases = file_get_contents('https://api.github.com/repos/' . $this->gitHubRepository . '/releases', false, $context);
$releases = json_decode($releases);
if (! isset($releases[0])) {
throw new \Exception('API error - no release found at GitHub repository ' . $this->gitHubRepository);
}
$version = $releases[0]->tag_name;
$url = $releases[0]->assets[0]->browser_download_url;
return [ $version, $url ];
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (empty(\Phar::running())) {
throw new \Exception(self::SELF_UPDATE_COMMAND_NAME . ' only works when running the phar version of ' . $this->applicationName . '.');
}
$localFilename = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
$programName = basename($localFilename);
$tempFilename = dirname($localFilename) . '/' . basename($localFilename, '.phar') . '-temp.phar';
// check for permissions in local filesystem before start connection process
if (! is_writable($tempDirectory = dirname($tempFilename))) {
throw new \Exception(
$programName . ' update failed: the "' . $tempDirectory .
'" directory used to download the temp file could not be written'
);
}
if (! is_writable($localFilename)) {
throw new \Exception(
$programName . ' update failed: the "' . $localFilename . '" file could not be written (execute with sudo)'
);
}
list( $latest, $downloadUrl ) = $this->getLatestReleaseFromGithub();
if ($this->currentVersion == $latest) {
$output->writeln('No update available');
return;
}
$fs = new sfFilesystem();
$output->writeln('Downloading ' . $this->applicationName . ' (' . $this->gitHubRepository . ') ' . $latest);
$fs->copy($downloadUrl, $tempFilename);
$output->writeln('Download finished');
try {
\error_reporting(E_ALL); // supress notices
@chmod($tempFilename, 0777 & ~umask());
// test the phar validity
$phar = new \Phar($tempFilename);
// free the variable to unlock the file
unset($phar);
@rename($tempFilename, $localFilename);
$output->writeln('<info>Successfully updated ' . $programName . '</info>');
$this->_exit();
} catch (\Exception $e) {
@unlink($tempFilename);
if (! $e instanceof \UnexpectedValueException && ! $e instanceof \PharException) {
throw $e;
}
$output->writeln('<error>The download is corrupted (' . $e->getMessage() . ').</error>');
$output->writeln('<error>Please re-run the self-update command to try again.</error>');
}
}
/**
* Stop execution
*
* This is a workaround to prevent warning of dispatcher after replacing
* the phar file.
*
* @return void
*/
protected function _exit()
{
exit;
}
}
<?php
namespace Consolidation\Log;
use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\StringInput;
/**
* Replacement for Symfony\Component\Console\Logger\ConsoleLogger.
* Each of the different log level messages are routed through the
* corresponding SymfonyStyle formatting method. Log messages are
* always sent to stderr if the provided output object implements
* ConsoleOutputInterface.
*
* Note that this class could extend ConsoleLogger if some methods
* of that class were declared 'protected' instead of 'private'.
*
* @author Greg Anderson <greg.1.anderson@greenknowe.org>
*/
class Logger extends AbstractLogger implements StylableLoggerInterface // extends ConsoleLogger
{
/**
* @var OutputInterface
*/
protected $output;
/**
* @var OutputInterface
*/
protected $error;
/**
* @var LogOutputStylerInterface
*/
protected $outputStyler;
/**
* @var OutputInterface|SymfonyStyle|other
*/
protected $outputStreamWrapper;
protected $errorStreamWrapper;
protected $formatFunctionMap = [
LogLevel::EMERGENCY => 'error',
LogLevel::ALERT => 'error',
LogLevel::CRITICAL => 'error',
LogLevel::ERROR => 'error',
LogLevel::WARNING => 'warning',
LogLevel::NOTICE => 'note',
LogLevel::INFO => 'note',
LogLevel::DEBUG => 'note',
ConsoleLogLevel::SUCCESS => 'success',
];
/**
* @param OutputInterface $output
* @param array $verbosityLevelMap
* @param array $formatLevelMap
* @param array $formatFunctionMap
*/
public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array(), array $formatFunctionMap = array())
{
$this->output = $output;
$this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
$this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
$this->formatFunctionMap = $formatFunctionMap + $this->formatFunctionMap;
}
public function setLogOutputStyler(LogOutputStylerInterface $outputStyler, array $formatFunctionMap = array())
{
$this->outputStyler = $outputStyler;
$this->formatFunctionMap = $formatFunctionMap + $this->formatFunctionMap;
$this->outputStreamWrapper = null;
$this->errorStreamWrapper = null;
}
public function getLogOutputStyler()
{
if (!isset($this->outputStyler)) {
$this->outputStyler = new SymfonyLogOutputStyler();
}
return $this->outputStyler;
}
protected function getOutputStream()
{
return $this->output;
}
protected function getErrorStream()
{
if (!isset($this->error)) {
$output = $this->getOutputStream();
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->error = $output;
}
return $this->error;
}
public function setOutputStream($output)
{
$this->output = $output;
$this->outputStreamWrapper = null;
}
public function setErrorStream($error)
{
$this->error = $error;
$this->errorStreamWrapper = null;
}
protected function getOutputStreamWrapper()
{
if (!isset($this->outputStreamWrapper)) {
$this->outputStreamWrapper = $this->getLogOutputStyler()->createOutputWrapper($this->getOutputStream());
}
return $this->outputStreamWrapper;
}
protected function getErrorStreamWrapper()
{
if (!isset($this->errorStreamWrapper)) {
$this->errorStreamWrapper = $this->getLogOutputStyler()->createOutputWrapper($this->getErrorStream());
}
return $this->errorStreamWrapper;
}
protected function getOutputStreamForLogLevel($level)
{
// Write to the error output if necessary and available.
// Usually, loggers that log to a terminal should send
// all log messages to stderr.
if (array_key_exists($level, $this->formatLevelMap) && ($this->formatLevelMap[$level] !== self::ERROR)) {
return $this->getOutputStreamWrapper();
}
return $this->getErrorStreamWrapper();
}
/**
* {@inheritdoc}
*/
public function log($level, $message, array $context = array())
{
// We use the '_level' context variable to allow log messages
// to be logged at one level (e.g. NOTICE) and formatted at another
// level (e.g. SUCCESS). This helps in instances where we want
// to style log messages at a custom log level that might not
// be available in all loggers. If the logger does not recognize
// the log level, then it is treated like the original log level.
if (array_key_exists('_level', $context) && array_key_exists($context['_level'], $this->verbosityLevelMap)) {
$level = $context['_level'];
}
// It is a runtime error if someone logs at a log level that
// we do not recognize.
if (!isset($this->verbosityLevelMap[$level])) {
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
}
// Write to the error output if necessary and available.
// Usually, loggers that log to a terminal should send
// all log messages to stderr.
$outputStreamWrapper = $this->getOutputStreamForLogLevel($level);
// Ignore messages that are not at the right verbosity level
if ($this->getOutputStream()->getVerbosity() >= $this->verbosityLevelMap[$level]) {
$this->doLog($outputStreamWrapper, $level, $message, $context);
}
}
/**
* Interpolate and style the message, and then send it to the log.
*/
protected function doLog($outputStreamWrapper, $level, $message, $context)
{
$formatFunction = 'log';
if (array_key_exists($level, $this->formatFunctionMap)) {
$formatFunction = $this->formatFunctionMap[$level];
}
$interpolated = $this->interpolate(
$message,
$this->getLogOutputStyler()->style($context)
);
$this->getLogOutputStyler()->$formatFunction(
$outputStreamWrapper,
$level,
$interpolated,
$context
);
}
public function success($message, array $context = array())
{
$this->log(ConsoleLogLevel::SUCCESS, $message, $context);
}
// The functions below could be eliminated if made `protected` intead
// of `private` in ConsoleLogger
const INFO = 'info';
const ERROR = 'error';
/**
* @var OutputInterface
*/
//private $output;
/**
* @var array
*/
private $verbosityLevelMap = [
LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
ConsoleLogLevel::SUCCESS => OutputInterface::VERBOSITY_NORMAL,
];
/**
* @var array
*
* Send all log messages to stderr. Symfony should have the same default.
* See: https://en.wikipedia.org/wiki/Standard_streams
* "Standard error was added to Unix after several wasted phototypesetting runs ended with error messages being typeset instead of displayed on the user's terminal."
*/
private $formatLevelMap = [
LogLevel::EMERGENCY => self::ERROR,
LogLevel::ALERT => self::ERROR,
LogLevel::CRITICAL => self::ERROR,
LogLevel::ERROR => self::ERROR,
LogLevel::WARNING => self::ERROR,
LogLevel::NOTICE => self::ERROR,
LogLevel::INFO => self::ERROR,
LogLevel::DEBUG => self::ERROR,
ConsoleLogLevel::SUCCESS => self::ERROR,
];
/**
* Interpolates context values into the message placeholders.
*
* @author PHP Framework Interoperability Group
*
* @param string $message
* @param array $context
*
* @return string
*/
private function interpolate($message, array $context)
{
// build a replacement array with braces around the context keys
$replace = array();
foreach ($context as $key => $val) {
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
$replace[sprintf('{%s}', $key)] = $val;
}
}
// interpolate replacement values into the message and return
return strtr($message, $replace);
}
}
<?php
namespace Consolidation\Log;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\StringInput;
/**
* StylableLoggerInterface indicates that a logger
* can receive a LogOutputStyler.
*
* @author Greg Anderson <greg.1.anderson@greenknowe.org>
*/
interface StylableLoggerInterface
{
public function setLogOutputStyler(LogOutputStylerInterface $outputStyler, array $formatFunctionMap = array());
}
<?php
namespace Consolidation\Log;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Allow a log message to by styled.
*
* Styling happens in two phases:
*
* 1. Prior to message interpolation, context variables are styled
* via the 'style()' method, using styles provided in the context
* under the '_style' key, and also using styles provided by the
* 'defaultStyles()' method.
*
* @see Symfony\Component\Console\Logger\ConsoleLogger::interpolate()
*
* 2. After message interpolation, an appropriate method based on the
* log level will be called. StyledConsoleLogger::$formatFunctionMap
* is used to map the LogLevel to a LogOutputStylerInterface method to call.
*
* It is possible to select the exact class to use as the log styler
* in the constructor of StyledConsoleLogger, and the mapping from
* LogLevel to format function can also be extended. It is possible to
* add new format methods not defined here, if desired, so long as
* any method named in the format function map is implemented by the
* selected log styler class.
*/
interface LogOutputStylerInterface
{
const STYLE_CONTEXT_KEY = '_style';
/**
* Return an array of default styles to use in an application.
* The key of the style is the variable name that the style
* should be applied to (or '*' to match all variables that have
* no specific style set), and the value is the contents of the
* Symfony style tag to wrap around the variable value.
*
* Example:
* message: 'Running {command}'
* context: ['command' => 'pwd']
* default styles: ['*' => 'info']
* result: 'Running <info>pwd</>'
*/
public function defaultStyles();
/**
* Apply styles specified in the STYLE_CONTEXT_KEY context variable to
* the other named variables stored in the context. The styles from
* the context are unioned with the default styles.
*/
public function style($context);
/**
* Create a wrapper object for the output stream. If this styler
* does not require an output wrapper, it should just return
* its $output parameter.
*/
public function createOutputWrapper(OutputInterface $output);
/**
* Print an ordinary log message, usually unstyled.
*/
public function log($output, $level, $message, $context);
/**
* Print a success message.
*/
public function success($output, $level, $message, $context);
/**
* Print an error message. Used when log level is:
* - LogLevel::EMERGENCY
* - LogLevel::ALERT
* - LogLevel::CRITICAL
* - LogLevel::ERROR
*/
public function error($output, $level, $message, $context);
/**
* Print a warning message. Used when log level is:
* - LogLevel::WARNING
*/
public function warning($output, $level, $message, $context);
/**
* Print a note. Similar to 'text', but may contain additional
* styling (e.g. the task name). Used when log level is:
* - LogLevel::NOTICE
* - LogLevel::INFO
* - LogLevel::DEBUG
*
* IMPORTANT: Symfony loggers only display LogLevel::NOTICE when the
* the verbosity level is VERBOSITY_VERBOSE, unless overridden in the
* constructor. Robo\Common\Logger emits LogLevel::NOTICE at
* VERBOSITY_NORMAL so that these messages will always be displayed.
*/
public function note($output, $level, $message, $context);
/**
* Print an error message. Not used by default by StyledConsoleLogger.
*/
public function caution($output, $level, $message, $context);
}
<?php
namespace Consolidation\Log;
/**
* Additional log levels for use in Console applications.
*
* ConsoleLogLevels may be used by methods of Symfony Command
* in applications where it is known that the Consolidation logger
* is in use. These log levels provide access to the 'success'
* styled output method. Code in reusable libraries that may
* be used with a standard Psr-3 logger should aviod using these
* log levels.
*
* All ConsoleLogLevels should be interpreted as LogLevel\NOTICE.
*
* @author Greg Anderson <greg.1.anderson@greenknowe.org>
*/
class ConsoleLogLevel extends \Psr\Log\LogLevel
{
/**
* Command successfully completed some operation.
* Displayed at VERBOSITY_NORMAL.
*/
const SUCCESS = 'success';
}
<?php
namespace Consolidation\Log;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Style log messages with Symfony\Component\Console\Style\SymfonyStyle.
* No context variable styling is done.
*
* This is the appropriate styler to use if your desire is to replace
* the use of SymfonyStyle with a Psr-3 logger without changing the
* appearance of your application's output.
*/
class SymfonyLogOutputStyler implements LogOutputStylerInterface
{
public function defaultStyles()
{
return [];
}
public function style($context)
{
return $context;
}
public function createOutputWrapper(OutputInterface $output)
{
// SymfonyStyle & c. contain both input and output functions,
// but we only need the output methods here. Create a stand-in
// input object to satisfy the SymfonyStyle constructor.
return new SymfonyStyle(new StringInput(''), $output);
}
public function log($symfonyStyle, $level, $message, $context)
{
$symfonyStyle->text($message);
}
public function success($symfonyStyle, $level, $message, $context)
{
$symfonyStyle->success($message);
}
public function error($symfonyStyle, $level, $message, $context)
{
$symfonyStyle->error($message);
}
public function warning($symfonyStyle, $level, $message, $context)
{
$symfonyStyle->warning($message);
}
public function note($symfonyStyle, $level, $message, $context)
{
$symfonyStyle->note($message);
}
public function caution($symfonyStyle, $level, $message, $context)
{
$symfonyStyle->caution($message);
}
}
<?php
namespace Consolidation\Log;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\OutputStyle;
/**
* Styles log output based on format mappings provided in the constructor.
*
* Override for greater control.
*/
class LogOutputStyler extends UnstyledLogOutputStyler
{
const TASK_STYLE_INFO = 'fg=white;bg=cyan;options=bold';
const TASK_STYLE_SUCCESS = 'fg=white;bg=green;options=bold';
const TASK_STYLE_WARNING = 'fg=black;bg=yellow;options=bold;';
const TASK_STYLE_ERROR = 'fg=white;bg=red;options=bold';
protected $defaultStyles = [
'*' => LogLevel::INFO,
];
protected $labelStyles = [
LogLevel::EMERGENCY => self::TASK_STYLE_ERROR,
LogLevel::ALERT => self::TASK_STYLE_ERROR,
LogLevel::CRITICAL => self::TASK_STYLE_ERROR,
LogLevel::ERROR => self::TASK_STYLE_ERROR,
LogLevel::WARNING => self::TASK_STYLE_WARNING,
LogLevel::NOTICE => self::TASK_STYLE_INFO,
LogLevel::INFO => self::TASK_STYLE_INFO,
LogLevel::DEBUG => self::TASK_STYLE_INFO,
ConsoleLogLevel::SUCCESS => self::TASK_STYLE_SUCCESS,
];
protected $messageStyles = [
LogLevel::EMERGENCY => self::TASK_STYLE_ERROR,
LogLevel::ALERT => self::TASK_STYLE_ERROR,
LogLevel::CRITICAL => self::TASK_STYLE_ERROR,
LogLevel::ERROR => self::TASK_STYLE_ERROR,
LogLevel::WARNING => '',
LogLevel::NOTICE => '',
LogLevel::INFO => '',
LogLevel::DEBUG => '',
ConsoleLogLevel::SUCCESS => '',
];
public function __construct($labelStyles = [], $messageStyles = [])
{
$this->labelStyles = $labelStyles + $this->labelStyles;
$this->messageStyles = $messageStyles + $this->messageStyles;
}
/**
* {@inheritdoc}
*/
public function defaultStyles()
{
return $this->defaultStyles;
}
/**
* {@inheritdoc}
*/
public function style($context)
{
$context += ['_style' => []];
$context['_style'] += $this->defaultStyles();
foreach ($context as $key => $value) {
$styleKey = $key;
if (!isset($context['_style'][$styleKey])) {
$styleKey = '*';
}
if (is_string($value) && isset($context['_style'][$styleKey])) {
$style = $context['_style'][$styleKey];
$context[$key] = $this->wrapFormatString($context[$key], $style);
}
}
return $context;
}
/**
* Wrap a string in a format element.
*/
protected function wrapFormatString($string, $style)
{
if ($style) {
return "<{$style}>$string</>";
}
return $string;
}
/**
* Look up the label and message styles for the specified log level,
* and use the log level as the label for the log message.
*/
protected function formatMessageByLevel($level, $message, $context)
{
$label = $level;
return $this->formatMessage($label, $message, $context, $this->labelStyles[$level], $this->messageStyles[$level]);
}
/**
* Apply styling with the provided label and message styles.
*/
protected function formatMessage($label, $message, $context, $labelStyle, $messageStyle = '')
{
if (!empty($messageStyle)) {
$message = $this->wrapFormatString(" $message ", $messageStyle);
}
if (!empty($label)) {
$message = ' ' . $this->wrapFormatString("[$label]", $labelStyle) . ' ' . $message;
}
return $message;
}
}
<?php
namespace Consolidation\Log;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\OutputStyle;
/**
* Base class that provides basic unstyled output.
*/
class UnstyledLogOutputStyler implements LogOutputStylerInterface
{
public function createOutputWrapper(OutputInterface $output)
{
return $output;
}
/**
* {@inheritdoc}
*/
public function defaultStyles()
{
return [];
}
/**
* {@inheritdoc}
*/
public function style($context)
{
return $context;
}
/**
* {@inheritdoc}
*/
protected function write($output, $message, $context)
{
$output->writeln($message);
}
/**
* {@inheritdoc}
*/
public function log($output, $level, $message, $context)
{
return $this->write($output, $this->formatMessageByLevel($level, $message, $context), $context);
}
/**
* {@inheritdoc}
*/
public function success($output, $level, $message, $context)
{
return $this->write($output, $this->formatMessageByLevel($level, $message, $context), $context);
}
/**
* {@inheritdoc}
*/
public function error($output, $level, $message, $context)
{
return $this->write($output, $this->formatMessageByLevel($level, $message, $context), $context);
}
/**
* {@inheritdoc}
*/
public function warning($output, $level, $message, $context)
{
return $this->write($output, $this->formatMessageByLevel($level, $message, $context), $context);
}
/**
* {@inheritdoc}
*/
public function note($output, $level, $message, $context)
{
return $this->write($output, $this->formatMessageByLevel($level, $message, $context), $context);
}
/**
* {@inheritdoc}
*/
public function caution($output, $level, $message, $context)
{
return $this->write($output, $this->formatMessageByLevel($level, $message, $context), $context);
}
/**
* Look up the label and message styles for the specified log level,
* and use the log level as the label for the log message.
*/
protected function formatMessageByLevel($level, $message, $context)
{
return " [$level] $message";
}
}
<?php
namespace Consolidation\Log;
use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
/**
* LoggerManager is a PSR-3 logger that can delegate
* log messages to other loggers. This is ideal if
* you need to inject a logger into various objects
* in your application, but need to change the way that
* the application logs later.
*
* @author Greg Anderson <greg.1.anderson@greenknowe.org>
*/
class LoggerManager extends AbstractLogger implements StylableLoggerInterface
{
/** @var LoggerInterface[] */
protected $loggers = [];
/** @var LoggerInterface */
protected $fallbackLogger = null;
/** @var LogOutputStylerInterface */
protected $outputStyler;
/** @var array */
protected $formatFunctionMap = [];
/**
* reset removes all loggers from the manager.
*/
public function reset()
{
$this->loggers = [];
return $this;
}
/**
* setLogOutputStyler will remember a style that
* should be applied to every stylable logger
* added to this manager.
*/
public function setLogOutputStyler(LogOutputStylerInterface $outputStyler, array $formatFunctionMap = array())
{
$this->outputStyler = $outputStyler;
$this->formatFunctionMap = $this->formatFunctionMap;
}
/**
* add adds a named logger to the manager,
* replacing any logger of the same name.
*
* @param string $name Name of logger to add
* @param LoggerInterface $logger Logger to send messages to
*/
public function add($name, LoggerInterface $logger)
{
// If this manager has been given a log style,
// and the logger being added accepts a log
// style, then copy our style to the logger
// being added.
if ($this->outputStyler && $logger instanceof StylableLoggerInterface) {
$logger->setLogOutputStyler($this->outputStyler, $this->formatFunctionMap);
}
$this->loggers[$name] = $logger;
return $this;
}
/**
* remove a named logger from the manager.
*
* @param string $name Name of the logger to remove.
*/
public function remove($name)
{
unset($this->loggers[$name]);
return $this;
}
/**
* fallbackLogger provides a logger that will
* be used only in instances where someone logs
* to the logger manager at a time when there
* are no other loggers registered. If there is
* no fallback logger, then the log messages
* are simply dropped.
*
* @param LoggerInterface $logger Logger to use as the fallback logger
*/
public function fallbackLogger(LoggerInterface $logger)
{
$this->fallbackLogger = $logger;
return $this;
}
/**
* {@inheritdoc}
*/
public function log($level, $message, array $context = array())
{
foreach ($this->getLoggers() as $logger) {
$logger->log($level, $message, $context);
}
}
/**
* Return either the list of registered loggers,
* or a single-element list containing only the
* fallback logger.
*/
protected function getLoggers()
{
if (!empty($this->loggers)) {
return $this->loggers;
}
if (isset($this->fallbackLogger)) {
return [ $this->fallbackLogger ];
}
return [];
}
}
<?php
namespace Robo;
use Robo\Common\BuilderAwareTrait;
trait TaskAccessor
{
use BuilderAwareTrait;
/**
* Provides the collection builder with access to all of the
* protected 'task' methods available on this object.
*
* @param string $fn
* @param array $args
*
* @return null|\Robo\Collection\CollectionBuilder
*/
public function getBuiltTask($fn, $args)
{
if (preg_match('#^task[A-Z]#', $fn)) {
return call_user_func_array([$this, $fn], $args);
}
}
/**
* Alternative access to instantiate. Use:
*
* $this->task(Foo::class, $a, $b);
*
* instead of:
*
* $this->taskFoo($a, $b);
*
* The later form is preferred.
*
* @return \Robo\Collection\CollectionBuilder
*/
protected function task()
{
$args = func_get_args();
$name = array_shift($args);
$collectionBuilder = $this->collectionBuilder();
return $collectionBuilder->build($name, $args);
}
}
<?php
namespace Robo;
use SelfUpdate\SelfUpdateCommand;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;
class Application extends SymfonyApplication
{
/**
* @param string $name
* @param string $version
*/
public function __construct($name, $version)
{
parent::__construct($name, $version);
$this->getDefinition()
->addOption(
new InputOption('--simulate', null, InputOption::VALUE_NONE, 'Run in simulated mode (show what would have happened).')
);
$this->getDefinition()
->addOption(
new InputOption('--progress-delay', null, InputOption::VALUE_REQUIRED, 'Number of seconds before progress bar is displayed in long-running task collections. Default: 2s.', Config::DEFAULT_PROGRESS_DELAY)
);
$this->getDefinition()
->addOption(
new InputOption('--define', '-D', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Define a configuration item value.', [])
);
}
/**
* @param string $roboFile
* @param string $roboClass
*/
public function addInitRoboFileCommand($roboFile, $roboClass)
{
$createRoboFile = new Command('init');
$createRoboFile->setDescription("Intitalizes basic RoboFile in current dir");
$createRoboFile->setCode(function () use ($roboClass, $roboFile) {
$output = Robo::output();
$output->writeln("<comment> ~~~ Welcome to Robo! ~~~~ </comment>");
$output->writeln("<comment> " . basename($roboFile) . " will be created in the current directory </comment>");
file_put_contents(
$roboFile,
'<?php'
. "\n/**"
. "\n * This is project's console commands configuration for Robo task runner."
. "\n *"
. "\n * @see http://robo.li/"
. "\n */"
. "\nclass " . $roboClass . " extends \\Robo\\Tasks\n{\n // define public methods as commands\n}"
);
$output->writeln("<comment> Edit this file to add your commands! </comment>");
});
$this->add($createRoboFile);
}
/**
* Add self update command, do nothing if null is provided
*
* @param string $repository
* GitHub Repository for self update.
*/
public function addSelfUpdateCommand($repository = null)
{
if (!$repository || empty(\Phar::running())) {
return;
}
$selfUpdateCommand = new SelfUpdateCommand($this->getName(), $this->getVersion(), $repository);
$this->add($selfUpdateCommand);
}
}
<?php
namespace Robo;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Robo\Contract\ConfigAwareInterface;
use Robo\Common\ConfigAwareTrait;
use Robo\Config\GlobalOptionDefaultValuesInterface;
class GlobalOptionsEventListener implements EventSubscriberInterface, ConfigAwareInterface
{
use ConfigAwareTrait;
/**
* @var \Robo\Application
*/
protected $application;
/**
* @var string
*/
protected $prefix;
/**
* GlobalOptionsEventListener listener
*/
public function __construct()
{
$this->prefix = 'options';
}
/**
* Add a reference to the Symfony Console application object.
*
* @param \Robo\Application $application
*
* @return $this
*/
public function setApplication($application)
{
$this->application = $application;
return $this;
}
/**
* Stipulate the prefix to use for option injection.
*
* @param string $prefix
*
* @return $this
*/
public function setGlobalOptionsPrefix($prefix)
{
$this->prefix = $prefix;
return $this;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [ConsoleEvents::COMMAND => 'handleCommandEvent'];
}
/**
* Run all of our individual operations when a command event is received.
*/
public function handleCommandEvent(ConsoleCommandEvent $event)
{
$this->setGlobalOptions($event);
$this->setConfigurationValues($event);
}
/**
* Before a Console command runs, examine the global
* commandline options from the event Input, and set
* configuration values as appropriate.
*
* @param \Symfony\Component\Console\Event\ConsoleCommandEvent $event
*/
public function setGlobalOptions(ConsoleCommandEvent $event)
{
$config = $this->getConfig();
$input = $event->getInput();
$globalOptions = $config->get($this->prefix, []);
if ($config instanceof \Consolidation\Config\GlobalOptionDefaultValuesInterface) {
$globalOptions += $config->getGlobalOptionDefaultValues();
}
$globalOptions += $this->applicationOptionDefaultValues();
// Set any config value that has a defined global option (e.g. --simulate)
foreach ($globalOptions as $option => $default) {
$value = $input->hasOption($option) ? $input->getOption($option) : null;
// Unfortunately, the `?:` operator does not differentate between `0` and `null`
if (!isset($value)) {
$value = $default;
}
$config->set($this->prefix . '.' . $option, $value);
}
}
/**
* Examine the commandline --define / -D options, and apply the provided
* values to the active configuration.
*
* @param \Symfony\Component\Console\Event\ConsoleCommandEvent $event
*/
public function setConfigurationValues(ConsoleCommandEvent $event)
{
$config = $this->getConfig();
$input = $event->getInput();
// Also set any `-Dconfig.key=value` options from the commandline.
if ($input->hasOption('define')) {
$configDefinitions = $input->getOption('define');
foreach ($configDefinitions as $value) {
list($key, $value) = $this->splitConfigKeyValue($value);
$config->set($key, $value);
}
}
}
/**
* Split up the key=value config setting into its component parts. If
* the input string contains no '=' character, then the value will be 'true'.
*
* @param string $value
*
* @return array
*/
protected function splitConfigKeyValue($value)
{
$parts = explode('=', $value, 2);
$parts[] = true;
return $parts;
}
/**
* Get default option values from the Symfony Console application, if
* it is available.
*
* @return array
*/
protected function applicationOptionDefaultValues()
{
if (!$this->application) {
return [];
}
$result = [];
foreach ($this->application->getDefinition()->getOptions() as $key => $option) {
$result[$key] = $option->acceptValue() ? $option->getDefault() : null;
}
return $result;
}
}
<?php
namespace Robo\Contract;
/**
* Adapt OutputInterface or other output function to the VerbosityThresholdInterface.
*/
interface OutputAdapterInterface
{
/**
* @param int $verbosityThreshold
*
* @return bool
*/
public function verbosityMeetsThreshold($verbosityThreshold);
/**
* @param string $message
*/
public function writeMessage($message);
}
<?php
namespace Robo\Contract;
/**
* Any Robo tasks that implements this interface will
* be called when the task collection it is added to
* fails, and runs its rollback operation.
*
* Interface RollbackInterface
* @package Robo\Contract
*/
interface RollbackInterface extends TaskInterface
{
/**
* Revert an operation that can be rolled back
*/
public function rollback();
}
<?php
namespace Robo\Contract;
/**
* Task that implements this interface can be injected as a parameter for other task.
* This task can be represented as executable command.
*
* @package Robo\Contract
*/
interface SimulatedInterface extends TaskInterface
{
/**
* Called in place of `run()` for simulated tasks.
*
* @param null|array $context
*/
public function simulate($context);
}
<?php
namespace Robo\Contract;
/**
* Any Robo task that uses the Timer trait and
* implements ProgressIndicatorAwareInterface will
* display a progress bar while the timer is running.
* Call advanceProgressIndicator to advance the indicator.
*
* Interface ProgressIndicatorAwareInterface
* @package Robo\Contract
*/
interface ProgressIndicatorAwareInterface
{
/**
* @return int
*/
public function progressIndicatorSteps();
/**
* @param \Robo\Common\ProgressIndicator $progressIndicator
*/
public function setProgressIndicator($progressIndicator);
}
<?php
namespace Robo\Contract;
use Robo\Collection\CollectionBuilder;
interface BuilderAwareInterface
{
/**
* Set the builder reference
*
* @param \Robo\Collection\CollectionBuilder $builder
*/
public function setBuilder(CollectionBuilder $builder);
/**
* Get the builder reference
*
* @return \Robo\Collection\CollectionBuilder
*/
public function getBuilder();
}
<?php
namespace Robo\Contract;
/**
* Robo tasks that take multiple steps to complete should
* implement this interface.
*
* Interface ProgressInterface
* @package Robo\Contract
*/
interface ProgressInterface
{
/**
*
* @return int
*/
public function progressSteps();
}
<?php
namespace Robo\Contract;
use Robo\Contract\OutputAdapterInterface;
/**
* Record and determine whether the current verbosity level exceeds the
* desired threshold level to produce output.
*/
interface VerbosityThresholdInterface
{
const VERBOSITY_NORMAL = 1;
const VERBOSITY_VERBOSE = 2;
const VERBOSITY_VERY_VERBOSE = 3;
const VERBOSITY_DEBUG = 4;
/**
* @param int $verbosityThreshold
*
* @return $this
*/
public function setVerbosityThreshold($verbosityThreshold);
/**
* @return int
*/
public function verbosityThreshold();
/**
* @param \Robo\Contract\OutputAdapterInterface $outputAdapter
*/
public function setOutputAdapter(OutputAdapterInterface $outputAdapter);
/**
* @return \Robo\Contract\OutputAdapterInterface
*/
public function outputAdapter();
/**
* @return bool
*/
public function hasOutputAdapter();
/**
* @return int
*/
public function verbosityMeetsThreshold();
/**
* @param string $message
*/
public function writeMessage($message);
}
<?php
/**
* Marker interface for tasks that use the IO trait
*/
namespace Robo\Contract;
use Symfony\Component\Console\Input\InputAwareInterface;
interface IOAwareInterface extends OutputAwareInterface, InputAwareInterface
{
}
<?php
namespace Robo\Contract;
/**
* Task that implements this interface can be injected as a parameter for other task.
* This task can be represented as executable command.
*
* @package Robo\Contract
*/
interface CommandInterface
{
/**
* Returns command that can be executed.
* This method is used to pass generated command from one task to another.
*
* @return string
*/
public function getCommand();
}
<?php
namespace Robo\Contract;
/**
* If task prints anything to console
*
* Interface PrintedInterface
* @package Robo\Contract
*/
interface PrintedInterface
{
/**
* @return bool
*/
public function getPrinted();
}
<?php
namespace Robo\Contract;
interface WrappedTaskInterface extends TaskInterface
{
/**
* @return \Robo\Contract\TaskInterface
*/
public function original();
}
<?php
/**
* Provide OutputAwareInterface, not present in Symfony Console
*/
namespace Robo\Contract;
use Symfony\Component\Console\Output\OutputInterface;
interface OutputAwareInterface
{
/**
* Sets the Console Output.
*
* @param \Symfony\Component\Console\Output\OutputInterface $output
*/
public function setOutput(OutputInterface $output);
}
<?php
namespace Robo\Contract;
/**
* Any Robo tasks that implements this interface will
* be called when the task collection it is added to
* completes.
*
* Interface CompletionInterface
* @package Robo\Contract
*/
interface CompletionInterface extends TaskInterface
{
/**
* Revert an operation that can be rolled back
*/
public function complete();
}
<?php
namespace Robo\Contract;
interface ConfigAwareInterface extends \Consolidation\Config\ConfigAwareInterface
{
}
<?php
namespace Robo\Contract;
/**
* All Robo tasks should implement this interface.
* Task should be configured by chained methods.
*
* Interface TaskInterface
* @package Robo\Contract
*/
interface TaskInterface
{
/**
* @return \Robo\Result
*/
public function run();
}
<?php
namespace Robo\Contract;
interface InflectionInterface
{
/**
* Based on league/container inflection: http://container.thephpleague.com/inflectors/
*
* This allows us to run:
*
* (new SomeTask($args))
* ->inflect($this)
* ->initializer()
* ->...
*
* Instead of:
*
* (new SomeTask($args))
* ->setLogger($this->logger)
* ->initializer()
* ->...
*
* The reason `inflect` is better than the more explicit alternative is
* that subclasses of BaseTask that implement a new FooAwareInterface
* can override injectDependencies() as explained below, and add more
* dependencies that can be injected as needed.
*
* @param \Robo\Contract\InflectionInterface $parent
*/
public function inflect(InflectionInterface $parent);
/**
* Take all dependencies availble to this task and inject any that are
* needed into the provided task. The general pattern is that, for every
* FooAwareInterface that this class implements, it should test to see
* if the child also implements the same interface, and if so, should call
* $child->setFoo($this->foo).
*
* The benefits of this are pretty large. Any time an object that implements
* InflectionInterface is created, just call `$child->inflect($this)`, and
* any available optional dependencies will be hooked up via setter injection.
*
* The required dependencies of an object should be provided via constructor
* injection, not inflection.
*
* @param InflectionInterface $child An object created by this class that
* should have its dependencies injected.
*
* @see https://mwop.net/blog/2016-04-26-on-locators.html
*/
public function injectDependencies(InflectionInterface $child);
}
<?php
namespace Robo\Collection;
use Robo\Contract\TaskInterface;
use Robo\Contract\WrappedTaskInterface;
use Robo\Contract\ProgressIndicatorAwareInterface;
/**
* One element in a collection. Each element consists of a task
* all of its before tasks, and all of its after tasks.
*
* This class is internal to Collection; it should not be used directly.
*/
class Element
{
/**
* @var \Robo\Contract\TaskInterface
*/
protected $task;
/**
* @var \Robo\Contract\TaskInterface[]|callable[]
*/
protected $before = [];
/**
* @var \Robo\Contract\TaskInterface[]|callable[]
*/
protected $after = [];
public function __construct(TaskInterface $task)
{
$this->task = $task;
}
/**
* @param \Robo\Contract\TaskInterface|callable $before
* @param string $name
*/
public function before($before, $name)
{
if ($name) {
$this->before[$name] = $before;
} else {
$this->before[] = $before;
}
}
/**
* @param \Robo\Contract\TaskInterface|callable $after
* @param string $name
*/
public function after($after, $name)
{
if ($name) {
$this->after[$name] = $after;
} else {
$this->after[] = $after;
}
}
/**
* @return \Robo\Contract\TaskInterface[]|callable[]
*/
public function getBefore()
{
return $this->before;
}
/**
* @return \Robo\Contract\TaskInterface[]|callable[]
*/
public function getAfter()
{
return $this->after;
}
/**
* @return \Robo\Contract\TaskInterface
*/
public function getTask()
{
return $this->task;
}
/**
* @return \Robo\Contract\TaskInterface[]|callable[]
*/
public function getTaskList()
{
return array_merge($this->getBefore(), [$this->getTask()], $this->getAfter());
}
/**
* @return int
*/
public function progressIndicatorSteps()
{
$steps = 0;
foreach ($this->getTaskList() as $task) {
if ($task instanceof WrappedTaskInterface) {
$task = $task->original();
}
// If the task is a ProgressIndicatorAwareInterface, then it
// will advance the progress indicator a number of times.
if ($task instanceof ProgressIndicatorAwareInterface) {
$steps += $task->progressIndicatorSteps();
}
// We also advance the progress indicator once regardless
// of whether it is progress-indicator aware or not.
$steps++;
}
return $steps;
}
}
<?php
namespace Robo\Collection;
/**
* The temporary collection keeps track of the global collection of
* temporary cleanup tasks in instances where temporary-generating
* tasks are executed directly via their run() method, rather than
* as part of a collection.
*
* In general, temporary-generating tasks should always be run in
* a collection, as the cleanup functions registered with the
* Temporary collection will not run until requested.
*
* Since the results could be undefined if cleanup functions were called
* at arbitrary times during a program's execution, cleanup should only
* be done immeidately prior to program termination, when there is no
* danger of cleaning up after some unrelated task.
*
* An application need never use Temporary directly, save to
* call Temporary::wrap() inside loadTasks or loadShortcuts, and
* to call Temporary::complete() immediately prior to terminating.
* This is recommended, but not required; this function will be
* registered as a shutdown function, and called on termination.
*/
class Temporary
{
/**
* @var \Robo\Collection\Collection
*/
private static $collection;
/**
* Provides direct access to the collection of temporaries, if necessary.
*
* @return \Robo\Collection\Collection
*/
public static function getCollection()
{
if (!static::$collection) {
static::$collection = \Robo\Robo::getContainer()->get('collection');
register_shutdown_function(function () {
static::complete();
});
}
return static::$collection;
}
/**
* Call the complete method of all of the registered objects.
*/
public static function complete()
{
// Run the collection of tasks. This will also run the
// completion tasks.
$collection = static::getCollection();
$collection->run();
// Make sure that our completion functions do not run twice.
$collection->reset();
}
}
<?php
namespace Robo\Collection;
use Consolidation\AnnotatedCommand\Hooks\ProcessResultInterface;
use Consolidation\AnnotatedCommand\CommandData;
use Robo\Contract\TaskInterface;
use Robo\Result;
/**
* The collection process hook is added to the annotation command
* hook manager in Runner::configureContainer(). This hook will be
* called every time a command runs. If the command result is a
* \Robo\Contract\TaskInterface (in particular, \Robo\Collection\Collection),
* then we run the collection, and return the result. We ignore results
* of any other type.
*/
class CollectionProcessHook implements ProcessResultInterface
{
/**
* @param \Robo\Contract\TaskInterface|mixed $result
* @param \Consolidation\AnnotatedCommand\CommandData $commandData
*
* @return null|\Robo\Result
*/
public function process($result, CommandData $commandData)
{
if ($result instanceof TaskInterface) {
try {
return $result->run();
} catch (\Exception $e) {
return Result::fromException($result, $e);
}
}
}
}
<?php
namespace Robo\Collection;
trait loadTasks
{
/**
* Run a callback function on each item in a collection
*
* @param array $collection
*
* @return \Robo\Collection\TaskForEach|\Robo\Collection\CollectionBuilder
*/
protected function taskForEach($collection = [])
{
return $this->task(TaskForEach::class, $collection);
}
}
<?php
namespace Robo\Collection;
use Robo\Exception\AbortTasksException;
use Robo\Result;
use Robo\State\Data;
use Psr\Log\LogLevel;
use Robo\Contract\TaskInterface;
use Robo\Task\StackBasedTask;
use Robo\Task\BaseTask;
use Robo\TaskInfo;
use Robo\Contract\WrappedTaskInterface;
use Robo\Exception\TaskException;
use Robo\Exception\TaskExitException;
use Robo\Contract\CommandInterface;
use Robo\Contract\InflectionInterface;
use Robo\State\StateAwareInterface;
use Robo\State\StateAwareTrait;
/**
* Group tasks into a collection that run together. Supports
* rollback operations for handling error conditions.
*
* This is an internal class. Clients should use a CollectionBuilder
* rather than direct use of the Collection class. @see CollectionBuilder.
*
* Below, the example FilesystemStack task is added to a collection,
* and associated with a rollback task. If any of the operations in
* the FilesystemStack, or if any of the other tasks also added to
* the task collection should fail, then the rollback function is
* called. Here, taskDeleteDir is used to remove partial results
* of an unfinished task.
*/
class Collection extends BaseTask implements CollectionInterface, CommandInterface, StateAwareInterface
{
use StateAwareTrait;
/**
* @var \Robo\Collection\Element[]
*/
protected $taskList = [];
/**
* @var \Robo\Contract\TaskInterface[]
*/
protected $rollbackStack = [];
/**
* @var \Robo\Contract\TaskInterface[]
*/
protected $completionStack = [];
/**
* @var \Robo\Collection\CollectionInterface
*/
protected $parentCollection;
/**
* @var callable[]
*/
protected $deferredCallbacks = [];
/**
* @var string[]
*/
protected $messageStoreKeys = [];
/**
* Constructor.
*/
public function __construct()
{
$this->resetState();
}
/**
* @param int $interval
*/
public function setProgressBarAutoDisplayInterval($interval)
{
if (!$this->progressIndicator) {
return;
}
return $this->progressIndicator->setProgressBarAutoDisplayInterval($interval);
}
/**
* {@inheritdoc}
*/
public function add(TaskInterface $task, $name = self::UNNAMEDTASK)
{
$task = new CompletionWrapper($this, $task);
$this->addToTaskList($name, $task);
return $this;
}
/**
* {@inheritdoc}
*/
public function addCode(callable $code, $name = self::UNNAMEDTASK)
{
return $this->add(new CallableTask($code, $this), $name);
}
/**
* {@inheritdoc}
*/
public function addIterable($iterable, callable $code)
{
$callbackTask = (new IterationTask($iterable, $code, $this))->inflect($this);
return $this->add($callbackTask);
}
/**
* {@inheritdoc}
*/
public function rollback(TaskInterface $rollbackTask)
{
// Rollback tasks always try as hard as they can, and never report failures.
$rollbackTask = $this->ignoreErrorsTaskWrapper($rollbackTask);
return $this->wrapAndRegisterRollback($rollbackTask);
}
/**
* {@inheritdoc}
*/
public function rollbackCode(callable $rollbackCode)
{
// Rollback tasks always try as hard as they can, and never report failures.
$rollbackTask = $this->ignoreErrorsCodeWrapper($rollbackCode);
return $this->wrapAndRegisterRollback($rollbackTask);
}
/**
* {@inheritdoc}
*/
public function completion(TaskInterface $completionTask)
{
$collection = $this;
$completionRegistrationTask = new CallableTask(
function () use ($collection, $completionTask) {
$collection->registerCompletion($completionTask);
},
$this
);
$this->addToTaskList(self::UNNAMEDTASK, $completionRegistrationTask);
return $this;
}
/**
* {@inheritdoc}
*/
public function completionCode(callable $completionTask)
{
$completionTask = new CallableTask($completionTask, $this);
return $this->completion($completionTask);
}
/**
* {@inheritdoc}
*/
public function before($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK)
{
return $this->addBeforeOrAfter(__FUNCTION__, $name, $task, $nameOfTaskToAdd);
}
/**
* {@inheritdoc}
*/
public function after($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK)
{
return $this->addBeforeOrAfter(__FUNCTION__, $name, $task, $nameOfTaskToAdd);
}
/**
* {@inheritdoc}
*/
public function progressMessage($text, $context = [], $level = LogLevel::NOTICE)
{
$context += ['name' => 'Progress'];
$context += TaskInfo::getTaskContext($this);
return $this->addCode(
function () use ($level, $text, $context) {
$context += $this->getState()->getData();
$this->printTaskOutput($level, $text, $context);
}
);
}
/**
* @param \Robo\Contract\TaskInterface $rollbackTask
*
* @return $this
*/
protected function wrapAndRegisterRollback(TaskInterface $rollbackTask)
{
$collection = $this;
$rollbackRegistrationTask = new CallableTask(
function () use ($collection, $rollbackTask) {
$collection->registerRollback($rollbackTask);
},
$this
);
$this->addToTaskList(self::UNNAMEDTASK, $rollbackRegistrationTask);
return $this;
}
/**
* Add either a 'before' or 'after' function or task.
*
* @param string $method
* @param string $name
* @param callable|\Robo\Contract\TaskInterface $task
* @param string $nameOfTaskToAdd
*
* @return $this
*/
protected function addBeforeOrAfter($method, $name, $task, $nameOfTaskToAdd)
{
if (is_callable($task)) {
$task = new CallableTask($task, $this);
}
$existingTask = $this->namedTask($name);
$fn = [$existingTask, $method];
call_user_func($fn, $task, $nameOfTaskToAdd);
return $this;
}
/**
* Wrap the provided task in a wrapper that will ignore
* any errors or exceptions that may be produced. This
* is useful, for example, in adding optional cleanup tasks
* at the beginning of a task collection, to remove previous
* results which may or may not exist.
*
* TODO: Provide some way to specify which sort of errors
* are ignored, so that 'file not found' may be ignored,
* but 'permission denied' reported?
*
* @param \Robo\Contract\TaskInterface $task
*
* @return \Robo\Collection\CallableTask
*/
public function ignoreErrorsTaskWrapper(TaskInterface $task)
{
// If the task is a stack-based task, then tell it
// to try to run all of its operations, even if some
// of them fail.
if ($task instanceof StackBasedTask) {
$task->stopOnFail(false);
}
$ignoreErrorsInTask = function () use ($task) {
$data = [];
try {
$result = $this->runSubtask($task);
$message = $result->getMessage();
$data = $result->getData();
$data['exitcode'] = $result->getExitCode();
} catch (AbortTasksException $abortTasksException) {
throw $abortTasksException;
} catch (\Exception $e) {
$message = $e->getMessage();
}
return Result::success($task, $message, $data);
};
// Wrap our ignore errors callable in a task.
return new CallableTask($ignoreErrorsInTask, $this);
}
/**
* @param callable $task
*
* @return \Robo\Collection\CallableTask
*/
public function ignoreErrorsCodeWrapper(callable $task)
{
return $this->ignoreErrorsTaskWrapper(new CallableTask($task, $this));
}
/**
* Return the list of task names added to this collection.
*
* @return string[]
*/
public function taskNames()
{
return array_keys($this->taskList);
}
/**
* Test to see if a specified task name exists.
* n.b. before() and after() require that the named
* task exist; use this function to test first, if
* unsure.
*
* @param string $name
*
* @return bool
*/
public function hasTask($name)
{
return array_key_exists($name, $this->taskList);
}
/**
* Find an existing named task.
*
* @param string $name
* The name of the task to insert before. The named task MUST exist.
*
* @return \Robo\Collection\Element
* The task group for the named task. Generally this is only
* used to call 'before()' and 'after()'.
*/
protected function namedTask($name)
{
if (!$this->hasTask($name)) {
throw new \RuntimeException("Could not find task named $name");
}
return $this->taskList[$name];
}
/**
* Add a list of tasks to our task collection.
*
* @param \Robo\Contract\TaskInterface[] $tasks
* An array of tasks to run with rollback protection
*
* @return $this
*/
public function addTaskList(array $tasks)
{
foreach ($tasks as $name => $task) {
$this->add($task, $name);
}
return $this;
}
/**
* Add the provided task to our task list.
*
* @param string $name
* @param \Robo\Contract\TaskInterface $task
*
* @return $this
*/
protected function addToTaskList($name, TaskInterface $task)
{
// All tasks are stored in a task group so that we have a place
// to hang 'before' and 'after' tasks.
$taskGroup = new Element($task);
return $this->addCollectionElementToTaskList($name, $taskGroup);
}
/**
* @param int|string $name
* @param \Robo\Collection\Element $taskGroup
*
* @return $this
*/
protected function addCollectionElementToTaskList($name, Element $taskGroup)
{
// If a task name is not provided, then we'll let php pick
// the array index.
if (Result::isUnnamed($name)) {
$this->taskList[] = $taskGroup;
return $this;
}
// If we are replacing an existing task with the
// same name, ensure that our new task is added to
// the end.
$this->taskList[$name] = $taskGroup;
return $this;
}
/**
* Set the parent collection. This is necessary so that nested
* collections' rollback and completion tasks can be added to the
* top-level collection, ensuring that the rollbacks for a collection
* will run if any later task fails.
*
* @param \Robo\Collection\NestedCollectionInterface $parentCollection
*
* @return $this
*/
public function setParentCollection(NestedCollectionInterface $parentCollection)
{
$this->parentCollection = $parentCollection;
return $this;
}
/**
* Get the appropriate parent collection to use
*
* @return \Robo\Collection\CollectionInterface|$this
*/
public function getParentCollection()
{
return $this->parentCollection ? $this->parentCollection : $this;
}
/**
* Register a rollback task to run if there is any failure.
*
* Clients are free to add tasks to the rollback stack as
* desired; however, usually it is preferable to call
* Collection::rollback() instead. With that function,
* the rollback function will only be called if all of the
* tasks added before it complete successfully, AND some later
* task fails.
*
* One example of a good use-case for registering a callback
* function directly is to add a task that sends notification
* when a task fails.
*
* @param \Robo\Contract\TaskInterface $rollbackTask
* The rollback task to run on failure.
*
* @return null
*/
public function registerRollback(TaskInterface $rollbackTask)
{
if ($this->parentCollection) {
return $this->parentCollection->registerRollback($rollbackTask);
}
if ($rollbackTask) {
array_unshift($this->rollbackStack, $rollbackTask);
}
}
/**
* Register a completion task to run once all other tasks finish.
* Completion tasks run whether or not a rollback operation was
* triggered. They do not trigger rollbacks if they fail.
*
* The typical use-case for a completion function is to clean up
* temporary objects (e.g. temporary folders). The preferred
* way to do that, though, is to use Temporary::wrap().
*
* On failures, completion tasks will run after all rollback tasks.
* If one task collection is nested inside another task collection,
* then the nested collection's completion tasks will run as soon as
* the nested task completes; they are not deferred to the end of
* the containing collection's execution.
*
* @param \Robo\Contract\TaskInterface $completionTask
* The completion task to run at the end of all other operations.
*
* @return null
*/
public function registerCompletion(TaskInterface $completionTask)
{
if ($this->parentCollection) {
return $this->parentCollection->registerCompletion($completionTask);
}
if ($completionTask) {
// Completion tasks always try as hard as they can, and never report failures.
$completionTask = $this->ignoreErrorsTaskWrapper($completionTask);
$this->completionStack[] = $completionTask;
}
}
/**
* Return the count of steps in this collection
*
* @return int
*/
public function progressIndicatorSteps()
{
$steps = 0;
foreach ($this->taskList as $name => $taskGroup) {
$steps += $taskGroup->progressIndicatorSteps();
}
return $steps;
}
/**
* A Collection of tasks can provide a command via `getCommand()`
* if it contains a single task, and that task implements CommandInterface.
*
* @return string
*
* @throws \Robo\Exception\TaskException
*/
public function getCommand()
{
if (empty($this->taskList)) {
return '';
}
if (count($this->taskList) > 1) {
// TODO: We could potentially iterate over the items in the collection
// and concatenate the result of getCommand() from each one, and fail
// only if we encounter a command that is not a CommandInterface.
throw new TaskException($this, "getCommand() does not work on arbitrary collections of tasks.");
}
$taskElement = reset($this->taskList);
$task = $taskElement->getTask();
$task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
if ($task instanceof CommandInterface) {
return $task->getCommand();
}
throw new TaskException($task, get_class($task) . " does not implement CommandInterface, so can't be used to provide a command");
}
/**
* Run our tasks, and roll back if necessary.
*
* @return \Robo\Result
*/
public function run()
{
$result = $this->runWithoutCompletion();
$this->complete();
return $result;
}
/**
* @return \Robo\Result
*/
private function runWithoutCompletion()
{
$result = Result::success($this);
if (empty($this->taskList)) {
return $result;
}
$this->startProgressIndicator();
if ($result->wasSuccessful()) {
foreach ($this->taskList as $name => $taskGroup) {
$taskList = $taskGroup->getTaskList();
$result = $this->runTaskList($name, $taskList, $result);
if (!$result->wasSuccessful()) {
$this->fail();
return $result;
}
}
$this->taskList = [];
}
$this->stopProgressIndicator();
$result['time'] = $this->getExecutionTime();
return $result;
}
/**
* Run every task in a list, but only up to the first failure.
* Return the failing result, or success if all tasks run.
*
* @param string $name
* @param \Robo\Contract\TaskInterface[] $taskList
* @param \Robo\Result $result
*
* @return \Robo\Result
*
* @throws \Robo\Exception\TaskExitException
*/
private function runTaskList($name, array $taskList, Result $result)
{
try {
foreach ($taskList as $taskName => $task) {
$taskResult = $this->runSubtask($task);
$this->advanceProgressIndicator();
// If the current task returns an error code, then stop
// execution and signal a rollback.
if (!$taskResult->wasSuccessful()) {
return $taskResult;
}
// We accumulate our results into a field so that tasks that
// have a reference to the collection may examine and modify
// the incremental results, if they wish.
$key = Result::isUnnamed($taskName) ? $name : $taskName;
$result->accumulate($key, $taskResult);
// The result message will be the message of the last task executed.
$result->setMessage($taskResult->getMessage());
}
} catch (TaskExitException $exitException) {
$this->fail();
throw $exitException;
} catch (\Exception $e) {
// Tasks typically should not throw, but if one does, we will
// convert it into an error and roll back.
return Result::fromException($task, $e, $result->getData());
}
return $result;
}
/**
* Force the rollback functions to run
*
* @return $this
*/
public function fail()
{
$this->disableProgressIndicator();
$this->runRollbackTasks();
$this->complete();
return $this;
}
/**
* Force the completion functions to run
*
* @return $this
*/
public function complete()
{
$this->detatchProgressIndicator();
$this->runTaskListIgnoringFailures($this->completionStack);
$this->reset();
return $this;
}
/**
* Reset this collection, removing all tasks.
*
* @return $this
*/
public function reset()
{
$this->taskList = [];
$this->completionStack = [];
$this->rollbackStack = [];
return $this;
}
/**
* Run all of our rollback tasks.
*
* Note that Collection does not implement RollbackInterface, but
* it may still be used as a task inside another task collection
* (i.e. you can nest task collections, if desired).
*/
protected function runRollbackTasks()
{
$this->runTaskListIgnoringFailures($this->rollbackStack);
// Erase our rollback stack once we have finished rolling
// everything back. This will allow us to potentially use
// a command collection more than once (e.g. to retry a
// failed operation after doing some error recovery).
$this->rollbackStack = [];
}
/**
* @param \Robo\Contract\TaskInterface|\Robo\Collection\NestedCollectionInterface|\Robo\Contract\WrappedTaskInterface $task
*
* @return \Robo\Result
*/
protected function runSubtask($task)
{
$original = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
$this->setParentCollectionForTask($original, $this->getParentCollection());
if ($original instanceof InflectionInterface) {
$original->inflect($this);
}
if ($original instanceof StateAwareInterface) {
$original->setState($this->getState());
}
$this->doDeferredInitialization($original);
$taskResult = $task->run();
$taskResult = Result::ensureResult($task, $taskResult);
$this->doStateUpdates($original, $taskResult);
return $taskResult;
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param \Robo\State\Data $taskResult
*/
protected function doStateUpdates($task, Data $taskResult)
{
$this->updateState($taskResult);
$key = spl_object_hash($task);
if (array_key_exists($key, $this->messageStoreKeys)) {
$state = $this->getState();
list($stateKey, $sourceKey) = $this->messageStoreKeys[$key];
$value = empty($sourceKey) ? $taskResult->getMessage() : $taskResult[$sourceKey];
$state[$stateKey] = $value;
}
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $key
* @param string $source
*
* @return $this
*/
public function storeState($task, $key, $source = '')
{
$this->messageStoreKeys[spl_object_hash($task)] = [$key, $source];
return $this;
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $functionName
* @param string $stateKey
*
* @return $this
*/
public function deferTaskConfiguration($task, $functionName, $stateKey)
{
return $this->defer(
$task,
function ($task, $state) use ($functionName, $stateKey) {
$fn = [$task, $functionName];
$value = $state[$stateKey];
$fn($value);
}
);
}
/**
* Defer execution of a callback function until just before a task
* runs. Use this time to provide more settings for the task, e.g. from
* the collection's shared state, which is populated with the results
* of previous test runs.
*
* @param \Robo\Contract\TaskInterface $task
* @param callable $callback
*
* @return $this
*/
public function defer($task, $callback)
{
$this->deferredCallbacks[spl_object_hash($task)][] = $callback;
return $this;
}
/**
* @param \Robo\Contract\TaskInterface $task
*/
protected function doDeferredInitialization($task)
{
// If the task is a state consumer, then call its receiveState method
if ($task instanceof \Robo\State\Consumer) {
$task->receiveState($this->getState());
}
// Check and see if there are any deferred callbacks for this task.
$key = spl_object_hash($task);
if (!array_key_exists($key, $this->deferredCallbacks)) {
return;
}
// Call all of the deferred callbacks
foreach ($this->deferredCallbacks[$key] as $fn) {
$fn($task, $this->getState());
}
}
/**
* @param TaskInterface|NestedCollectionInterface|WrappedTaskInterface $task
* @param \Robo\Collection\CollectionInterface $parentCollection
*/
protected function setParentCollectionForTask($task, $parentCollection)
{
if ($task instanceof NestedCollectionInterface) {
$task->setParentCollection($parentCollection);
}
}
/**
* Run all of the tasks in a provided list, ignoring failures.
*
* You may force a failure by throwing a ForcedException in your rollback or
* completion task or callback.
*
* This is used to roll back or complete.
*
* @param \Robo\Contract\TaskInterface[] $taskList
*/
protected function runTaskListIgnoringFailures(array $taskList)
{
foreach ($taskList as $task) {
try {
$this->runSubtask($task);
} catch (AbortTasksException $abortTasksException) {
// If there's a forced exception, end the loop of tasks.
if ($message = $abortTasksException->getMessage()) {
$this->logger()->notice($message);
}
break;
} catch (\Exception $e) {
// Ignore rollback failures.
}
}
}
/**
* Give all of our tasks to the provided collection builder.
*
* @param \Robo\Collection\CollectionBuilder $builder
*/
public function transferTasks($builder)
{
foreach ($this->taskList as $name => $taskGroup) {
// TODO: We are abandoning all of our before and after tasks here.
// At the moment, transferTasks is only called under conditions where
// there will be none of these, but care should be taken if that changes.
$task = $taskGroup->getTask();
$builder->addTaskToCollection($task);
}
$this->reset();
}
}
<?php
namespace Robo\Collection;
use Robo\Task\BaseTask;
use Robo\Contract\TaskInterface;
use Robo\Contract\RollbackInterface;
use Robo\Contract\CompletionInterface;
use Robo\Contract\WrappedTaskInterface;
/**
* Creates a task wrapper that will manage rollback and collection
* management to a task when it runs. Tasks are automatically
* wrapped in a CompletionWrapper when added to a task collection.
*
* Clients may need to wrap their task in a CompletionWrapper if it
* creates temporary objects.
*
* @see \Robo\Task\Filesystem\loadTasks::taskTmpDir
*/
class CompletionWrapper extends BaseTask implements WrappedTaskInterface
{
/**
* @var \Robo\Collection\Collection
*/
private $collection;
/**
* @var \Robo\Contract\TaskInterface
*/
private $task;
/**
* @var NULL|\Robo\Contract\TaskInterface
*/
private $rollbackTask;
/**
* Create a CompletionWrapper.
*
* Temporary tasks are always wrapped in a CompletionWrapper, as are
* any tasks that are added to a collection. If a temporary task
* is added to a collection, then it is first unwrapped from its
* CompletionWrapper (via its original() method), and then added to a
* new CompletionWrapper for the collection it is added to.
*
* In this way, when the CompletionWrapper is finally executed, the
* task's rollback and completion handlers will be registered on
* whichever collection it was registered on.
*
* @todo Why not CollectionInterface the type of the $collection argument?
*
* @param \Robo\Collection\Collection $collection
* @param \Robo\Contract\TaskInterface $task
* @param \Robo\Contract\TaskInterface|null $rollbackTask
*/
public function __construct(Collection $collection, TaskInterface $task, TaskInterface $rollbackTask = null)
{
$this->collection = $collection;
$this->task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
$this->rollbackTask = $rollbackTask;
}
/**
* {@inheritdoc}
*/
public function original()
{
return $this->task;
}
/**
* Before running this task, register its rollback and completion
* handlers on its collection. The reason this class exists is to
* defer registration of rollback and completion tasks until 'run()' time.
*
* @return \Robo\Result
*/
public function run()
{
if ($this->rollbackTask) {
$this->collection->registerRollback($this->rollbackTask);
}
if ($this->task instanceof RollbackInterface) {
$this->collection->registerRollback(new CallableTask([$this->task, 'rollback'], $this->task));
}
if ($this->task instanceof CompletionInterface) {
$this->collection->registerCompletion(new CallableTask([$this->task, 'complete'], $this->task));
}
return $this->task->run();
}
/**
* Make this wrapper object act like the class it wraps.
*
* @param string $function
* @param array $args
*
* @return mixed
*/
public function __call($function, $args)
{
return call_user_func_array(array($this->task, $function), $args);
}
}
<?php
namespace Robo\Collection;
use Psr\Log\LogLevel;
use Robo\Contract\TaskInterface;
interface CollectionInterface extends NestedCollectionInterface
{
/**
* Unnamed tasks are assigned an arbitrary numeric index
* in the task list. Any numeric value may be used, but the
* UNNAMEDTASK constant is recommended for clarity.
*
* @var int
*/
const UNNAMEDTASK = 0;
/**
* Add a task or a list of tasks to our task collection. Each task
* will run via its 'run()' method once (and if) all of the tasks
* added before it complete successfully. If the task also implements
* RollbackInterface, then it will be rolled back via its 'rollback()'
* method ONLY if its 'run()' method completes successfully, and some
* task added after it fails.
*
* @param \Robo\Contract\TaskInterface $task
* The task to add to our collection.
* @param int|string $name
* An optional name for the task -- missing or UNNAMEDTASK for unnamed tasks.
* Names are used for positioning before and after tasks.
*
* @return $this
*/
public function add(TaskInterface $task, $name = self::UNNAMEDTASK);
/**
* Add arbitrary code to execute as a task.
*
* @param callable $code
* Code to execute as a task
* @param int|string $name
* An optional name for the task -- missing or UNNAMEDTASK for unnamed tasks.
* Names are used for positioning before and after tasks.
*
* @return $this
*/
public function addCode(callable $code, $name = self::UNNAMEDTASK);
/**
* Add arbitrary code that will be called once for every item in the
* provided array or iterable object. If the function result of the
* provided callback is a TaskInterface or Collection, then it will be
* executed.
*
* @param static|array $iterable
* A collection of things to iterate.
* @param callable $code
* A callback function to call for each item in the collection.
*
* @return $this
*/
public function addIterable($iterable, callable $code);
/**
* Add a rollback task to our task collection. A rollback task
* will execute ONLY if all of the tasks added before it complete
* successfully, AND some task added after it fails.
*
* @param \Robo\Contract\TaskInterface $rollbackTask
* The rollback task to add. Note that the 'run()' method of the
* task executes, not its 'rollback()' method. To use the 'rollback()'
* method, add the task via 'Collection::add()' instead.
*
* @return $this
*/
public function rollback(TaskInterface $rollbackTask);
/**
* Add arbitrary code to execute as a rollback.
*
* @param callable $rollbackTask
* Code to execute during rollback processing.
*
* @return $this
*/
public function rollbackCode(callable $rollbackTask);
/**
* Add a completion task to our task collection. A completion task
* will execute EITHER after all tasks succeed, OR immediatley after
* any task fails. Completion tasks never cause errors to be returned
* from Collection::run(), even if they fail.
*
* @param \Robo\Contract\TaskInterface $completionTask
* The completion task to add. Note that the 'run()' method of the
* task executes, just as if the task was added normally.
*
* @return $this
*/
public function completion(TaskInterface $completionTask);
/**
* Add arbitrary code to execute as a completion.
*
* @param callable $completionTask
* Code to execute after collection completes
*
* @return $this
*/
public function completionCode(callable $completionTask);
/**
* Add a task before an existing named task.
*
* @param string $name
* The name of the task to insert before. The named task MUST exist.
* @param callable|\Robo\Contract\TaskInterface $task
* The task to add.
* @param int|string $nameOfTaskToAdd
* The name of the task to add. If not provided, will be associated
* with the named task it was added before.
*
* @return $this
*/
public function before($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK);
/**
* Add a task after an existing named task.
*
* @param string $name
* The name of the task to insert before. The named task MUST exist.
* @param callable|\Robo\Contract\TaskInterface $task
* The task to add.
* @param int|string $nameOfTaskToAdd
* The name of the task to add. If not provided, will be associated
* with the named task it was added after.
*
* @return $this
*/
public function after($name, $task, $nameOfTaskToAdd = self::UNNAMEDTASK);
/**
* Print a progress message after Collection::run() has executed
* all of the tasks that were added prior to the point when this
* method was called. If one of the previous tasks fail, then this
* message will not be printed.
*
* @param string $text
* Message to print.
* @param array $context
* Extra context data for use by the logger. Note
* that the data from the collection state is merged with the provided context.
* @param \Psr\Log\LogLevel|string $level
* The log level to print the information at. Default is NOTICE.
*
* @return $this
*/
public function progressMessage($text, $context = [], $level = LogLevel::NOTICE);
}
<?php
namespace Robo\Collection;
use Robo\Result;
use Robo\Contract\TaskInterface;
use Robo\State\StateAwareInterface;
use Robo\State\Data;
/**
* Creates a task wrapper that converts any Callable into an
* object that can be used directly with a task collection.
*
* It is not necessary to use this class directly; Collection will
* automatically wrap Callables when they are added.
*/
class CallableTask implements TaskInterface
{
/**
* @var callable
*/
protected $fn;
/**
* @var \Robo\Contract\TaskInterface
*/
protected $reference;
public function __construct(callable $fn, TaskInterface $reference)
{
$this->fn = $fn;
$this->reference = $reference;
}
/**
* {@inheritdoc}
*/
public function run()
{
$result = call_user_func($this->fn, $this->getState());
// If the function returns no result, then count it
// as a success.
if (!isset($result)) {
$result = Result::success($this->reference);
}
// If the function returns a result, it must either return
// a \Robo\Result or an exit code. In the later case, we
// convert it to a \Robo\Result.
if (!$result instanceof Result) {
$result = new Result($this->reference, $result);
}
return $result;
}
/**
* @return \Robo\State\Data
*/
public function getState()
{
if ($this->reference instanceof StateAwareInterface) {
return $this->reference->getState();
}
return new Data();
}
}
<?php
namespace Robo\Collection;
use Consolidation\Config\Inject\ConfigForSetters;
use Robo\Config\Config;
use Psr\Log\LogLevel;
use Robo\Contract\InflectionInterface;
use Robo\Contract\TaskInterface;
use Robo\Contract\CompletionInterface;
use Robo\Contract\WrappedTaskInterface;
use Robo\Task\Simulator;
use ReflectionClass;
use Robo\Task\BaseTask;
use Robo\Contract\BuilderAwareInterface;
use Robo\Contract\CommandInterface;
use Robo\Contract\VerbosityThresholdInterface;
use Robo\State\StateAwareInterface;
use Robo\State\StateAwareTrait;
use Robo\Result;
/**
* Creates a collection, and adds tasks to it. The collection builder
* offers a streamlined chained-initialization mechanism for easily
* creating task groups. Facilities for creating working and temporary
* directories are also provided.
*
* ``` php
* <?php
* $result = $this->collectionBuilder()
* ->taskFilesystemStack()
* ->mkdir('g')
* ->touch('g/g.txt')
* ->rollback(
* $this->taskDeleteDir('g')
* )
* ->taskFilesystemStack()
* ->mkdir('g/h')
* ->touch('g/h/h.txt')
* ->taskFilesystemStack()
* ->mkdir('g/h/i/c')
* ->touch('g/h/i/i.txt')
* ->run()
* ?>
*
* In the example above, the `taskDeleteDir` will be called if
* ```
*/
class CollectionBuilder extends BaseTask implements NestedCollectionInterface, WrappedTaskInterface, CommandInterface, StateAwareInterface
{
use StateAwareTrait;
/**
* @var \Robo\Tasks
*/
protected $commandFile;
/**
* @var \Robo\Collection\CollectionInterface
*/
protected $collection;
/**
* @var \Robo\Contract\TaskInterface
*/
protected $currentTask;
/**
* @var bool
*/
protected $simulated;
/**
* @param \Robo\Tasks $commandFile
*/
public function __construct($commandFile)
{
$this->commandFile = $commandFile;
$this->resetState();
}
/**
* @param \League\Container\ContainerInterface $container
* @param \Robo\Tasks $commandFile
*
* @return static
*/
public static function create($container, $commandFile)
{
$builder = new self($commandFile);
$builder->setLogger($container->get('logger'));
$builder->setProgressIndicator($container->get('progressIndicator'));
$builder->setConfig($container->get('config'));
$builder->setOutputAdapter($container->get('outputAdapter'));
return $builder;
}
/**
* @param bool $simulated
*
* @return $this
*/
public function simulated($simulated = true)
{
$this->simulated = $simulated;
return $this;
}
/**
* @return bool
*/
public function isSimulated()
{
if (!isset($this->simulated)) {
$this->simulated = $this->getConfig()->get(Config::SIMULATE);
}
return $this->simulated;
}
/**
* Create a temporary directory to work in. When the collection
* completes or rolls back, the temporary directory will be deleted.
* Returns the path to the location where the directory will be
* created.
*
* @param string $prefix
* @param string $base
* @param bool $includeRandomPart
*
* @return string
*/
public function tmpDir($prefix = 'tmp', $base = '', $includeRandomPart = true)
{
// n.b. Any task that the builder is asked to create is
// automatically added to the builder's collection, and
// wrapped in the builder object. Therefore, the result
// of any call to `taskFoo()` from within the builder will
// always be `$this`.
return $this->taskTmpDir($prefix, $base, $includeRandomPart)->getPath();
}
/**
* Create a working directory to hold results. A temporary directory
* is first created to hold the intermediate results. After the
* builder finishes, the work directory is moved into its final location;
* any results already in place will be moved out of the way and
* then deleted.
*
* @param string $finalDestination
* The path where the working directory will be moved once the task
* collection completes.
*
* @return string
*/
public function workDir($finalDestination)
{
// Creating the work dir task in this context adds it to our task collection.
return $this->taskWorkDir($finalDestination)->getPath();
}
/**
* @return $this
*/
public function addTask(TaskInterface $task)
{
$this->getCollection()->add($task);
return $this;
}
/**
* Add arbitrary code to execute as a task.
*
* @see \Robo\Collection\CollectionInterface::addCode
*
* @param callable $code
* @param int|string $name
*
* @return $this
*/
public function addCode(callable $code, $name = \Robo\Collection\CollectionInterface::UNNAMEDTASK)
{
$this->getCollection()->addCode($code, $name);
return $this;
}
/**
* Add a list of tasks to our task collection.
*
* @param \Robo\Contract\TaskInterface[] $tasks
* An array of tasks to run with rollback protection
*
* @return $this
*/
public function addTaskList(array $tasks)
{
$this->getCollection()->addTaskList($tasks);
return $this;
}
/**
* @return $this
*/
public function rollback(TaskInterface $task)
{
// Ensure that we have a collection if we are going to add
// a rollback function.
$this->getCollection()->rollback($task);
return $this;
}
/**
* @return $this
*/
public function rollbackCode(callable $rollbackCode)
{
$this->getCollection()->rollbackCode($rollbackCode);
return $this;
}
/**
* @return $this
*/
public function completion(TaskInterface $task)
{
$this->getCollection()->completion($task);
return $this;
}
/**
* @return $this
*/
public function completionCode(callable $completionCode)
{
$this->getCollection()->completionCode($completionCode);
return $this;
}
/**
* @param string $text
* @param array $context
* @param string $level
*
* @return $this
*/
public function progressMessage($text, $context = [], $level = LogLevel::NOTICE)
{
$this->getCollection()->progressMessage($text, $context, $level);
return $this;
}
/**
* @return $this
*/
public function setParentCollection(NestedCollectionInterface $parentCollection)
{
$this->getCollection()->setParentCollection($parentCollection);
return $this;
}
/**
* Called by the factory method of each task; adds the current
* task to the task builder.
*
* TODO: protected
*
* @param \Robo\Contract\TaskInterface $task
*
* @return $this
*/
public function addTaskToCollection($task)
{
// Postpone creation of the collection until the second time
// we are called. At that time, $this->currentTask will already
// be populated. We call 'getCollection()' so that it will
// create the collection and add the current task to it.
// Note, however, that if our only tasks implements NestedCollectionInterface,
// then we should force this builder to use a collection.
if (!$this->collection && (isset($this->currentTask) || ($task instanceof NestedCollectionInterface))) {
$this->getCollection();
}
$this->currentTask = $task;
if ($this->collection) {
$this->collection->add($task);
}
return $this;
}
/**
* @return \Robo\State\Data
*/
public function getState()
{
$collection = $this->getCollection();
return $collection->getState();
}
/**
* @param int|string $key
* @param mixed $source
*
* @return $this
*/
public function storeState($key, $source = '')
{
return $this->callCollectionStateFunction(__FUNCTION__, func_get_args());
}
/**
* @param string $functionName
* @param int|string $stateKey
*
* @return $this
*/
public function deferTaskConfiguration($functionName, $stateKey)
{
return $this->callCollectionStateFunction(__FUNCTION__, func_get_args());
}
/**
* @param callable$callback
*
* @return $this
*/
public function defer($callback)
{
return $this->callCollectionStateFunction(__FUNCTION__, func_get_args());
}
/**
* @param string $functionName
* @param array $args
*
* @return $this
*/
protected function callCollectionStateFunction($functionName, $args)
{
$currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
array_unshift($args, $currentTask);
$collection = $this->getCollection();
$fn = [$collection, $functionName];
call_user_func_array($fn, $args);
return $this;
}
/**
* @param string $functionName
* @param array $args
*
* @return $this
*
* @deprecated Use ::callCollectionStateFunction() instead.
*/
protected function callCollectionStateFuntion($functionName, $args)
{
return $this->callCollectionStateFunction($functionName, $args);
}
/**
* @param int $verbosityThreshold
*
* @return $this
*/
public function setVerbosityThreshold($verbosityThreshold)
{
$currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
if ($currentTask) {
$currentTask->setVerbosityThreshold($verbosityThreshold);
return $this;
}
parent::setVerbosityThreshold($verbosityThreshold);
return $this;
}
/**
* Return the current task for this collection builder.
* TODO: Not needed?
*
* @return \Robo\Contract\TaskInterface
*/
public function getCollectionBuilderCurrentTask()
{
return $this->currentTask;
}
/**
* Create a new builder with its own task collection
*
* @return \Robo\Collection\CollectionBuilder
*/
public function newBuilder()
{
$collectionBuilder = new self($this->commandFile);
$collectionBuilder->inflect($this);
$collectionBuilder->simulated($this->isSimulated());
$collectionBuilder->setVerbosityThreshold($this->verbosityThreshold());
$collectionBuilder->setState($this->getState());
return $collectionBuilder;
}
/**
* Calling the task builder with methods of the current
* task calls through to that method of the task.
*
* There is extra complexity in this function that could be
* simplified if we attached the 'LoadAllTasks' and custom tasks
* to the collection builder instead of the RoboFile. While that
* change would be a better design overall, it would require that
* the user do a lot more work to set up and use custom tasks.
* We therefore take on some additional complexity here in order
* to allow users to maintain their tasks in their RoboFile, which
* is much more convenient.
*
* Calls to $this->collectionBuilder()->taskFoo() cannot be made
* directly because all of the task methods are protected. These
* calls will therefore end up here. If the method name begins
* with 'task', then it is eligible to be used with the builder.
*
* When we call getBuiltTask, below, it will use the builder attached
* to the commandfile to build the task. However, this is not what we
* want: the task needs to be built from THIS collection builder, so that
* it will be affected by whatever state is active in this builder.
* To do this, we have two choices: 1) save and restore the builder
* in the commandfile, or 2) clone the commandfile and set this builder
* on the copy. 1) is vulnerable to failure in multithreaded environments
* (currently not supported), while 2) might cause confusion if there
* is shared state maintained in the commandfile, which is in the
* domain of the user.
*
* Note that even though we are setting up the commandFile to
* use this builder, getBuiltTask always creates a new builder
* (which is constructed using all of the settings from the
* commandFile's builder), and the new task is added to that.
* We therefore need to transfer the newly built task into this
* builder. The temporary builder is discarded.
*
* @param string $fn
* @param array $args
*
* @return $this|mixed
*/
public function __call($fn, $args)
{
if (preg_match('#^task[A-Z]#', $fn) && (method_exists($this->commandFile, 'getBuiltTask'))) {
$saveBuilder = $this->commandFile->getBuilder();
$this->commandFile->setBuilder($this);
$temporaryBuilder = $this->commandFile->getBuiltTask($fn, $args);
$this->commandFile->setBuilder($saveBuilder);
if (!$temporaryBuilder) {
throw new \BadMethodCallException("No such method $fn: task does not exist in " . get_class($this->commandFile));
}
$temporaryBuilder->getCollection()->transferTasks($this);
return $this;
}
if (!isset($this->currentTask)) {
throw new \BadMethodCallException("No such method $fn: current task undefined in collection builder.");
}
// If the method called is a method of the current task,
// then call through to the current task's setter method.
$result = call_user_func_array([$this->currentTask, $fn], $args);
// If something other than a setter method is called, then return its result.
$currentTask = ($this->currentTask instanceof WrappedTaskInterface) ? $this->currentTask->original() : $this->currentTask;
if (isset($result) && ($result !== $currentTask)) {
return $result;
}
return $this;
}
/**
* Construct the desired task and add it to this builder.
*
* @param string|object $name
* @param array $args
*
* @return $this
*/
public function build($name, $args)
{
$reflection = new ReflectionClass($name);
$task = $reflection->newInstanceArgs($args);
if (!$task) {
throw new \RuntimeException("Can not construct task $name");
}
$task = $this->fixTask($task, $args);
$this->configureTask($name, $task);
return $this->addTaskToCollection($task);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param array $args
*
* @return \Robo\Collection\CompletionWrapper|\Robo\Task\Simulator
*/
protected function fixTask($task, $args)
{
if ($task instanceof InflectionInterface) {
$task->inflect($this);
}
if ($task instanceof BuilderAwareInterface) {
$task->setBuilder($this);
}
if ($task instanceof VerbosityThresholdInterface) {
$task->setVerbosityThreshold($this->verbosityThreshold());
}
// Do not wrap our wrappers.
if ($task instanceof CompletionWrapper || $task instanceof Simulator) {
return $task;
}
// Remember whether or not this is a task before
// it gets wrapped in any decorator.
$isTask = $task instanceof TaskInterface;
$isCollection = $task instanceof NestedCollectionInterface;
// If the task implements CompletionInterface, ensure
// that its 'complete' method is called when the application
// terminates -- but only if its 'run' method is called
// first. If the task is added to a collection, then the
// task will be unwrapped via its `original` method, and
// it will be re-wrapped with a new completion wrapper for
// its new collection.
if ($task instanceof CompletionInterface) {
$task = new CompletionWrapper(Temporary::getCollection(), $task);
}
// If we are in simulated mode, then wrap any task in
// a TaskSimulator.
if ($isTask && !$isCollection && ($this->isSimulated())) {
$task = new \Robo\Task\Simulator($task, $args);
$task->inflect($this);
}
return $task;
}
/**
* Check to see if there are any setter methods defined in configuration
* for this task.
*
* @param string $taskClass
* @param \Robo\Contract\TaskInterface $task
*/
protected function configureTask($taskClass, $task)
{
$taskClass = static::configClassIdentifier($taskClass);
$configurationApplier = new ConfigForSetters($this->getConfig(), $taskClass, 'task.');
$configurationApplier->apply($task, 'settings');
// TODO: If we counted each instance of $taskClass that was called from
// this builder, then we could also apply configuration from
// "task.{$taskClass}[$N].settings"
// TODO: If the builder knew what the current command name was,
// then we could also search for task configuration under
// command-specific keys such as "command.{$commandname}.task.{$taskClass}.settings".
}
/**
* When we run the collection builder, run everything in the collection.
*
* @return \Robo\Result
*/
public function run()
{
$this->startTimer();
$result = $this->runTasks();
$this->stopTimer();
$result['time'] = $this->getExecutionTime();
$result->mergeData($this->getState()->getData());
return $result;
}
/**
* If there is a single task, run it; if there is a collection, run
* all of its tasks.
*
* @return \Robo\Result
*/
protected function runTasks()
{
if (!$this->collection && $this->currentTask) {
$result = $this->currentTask->run();
return Result::ensureResult($this->currentTask, $result);
}
return $this->getCollection()->run();
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
if (!$this->collection && $this->currentTask) {
$task = $this->currentTask;
$task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
if ($task instanceof CommandInterface) {
return $task->getCommand();
}
}
return $this->getCollection()->getCommand();
}
/**
* @return \Robo\Collection\CollectionInterface
*/
public function original()
{
return $this->getCollection();
}
/**
* Return the collection of tasks associated with this builder.
*
* @return \Robo\Collection\CollectionInterface
*/
public function getCollection()
{
if (!isset($this->collection)) {
$this->collection = new Collection();
$this->collection->inflect($this);
$this->collection->setState($this->getState());
$this->collection->setProgressBarAutoDisplayInterval($this->getConfig()->get(Config::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL));
if (isset($this->currentTask)) {
$this->collection->add($this->currentTask);
}
}
return $this->collection;
}
}
<?php
namespace Robo\Collection;
interface NestedCollectionInterface
{
/**
* @param \Robo\Collection\NestedCollectionInterface $parentCollection
*
* @return $this
*/
public function setParentCollection(NestedCollectionInterface $parentCollection);
}
<?php
namespace Robo\Collection;
use Robo\Result;
use Robo\TaskInfo;
use Robo\Task\BaseTask;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Creates a task wrapper that converts any Callable into an
* object that will execute the callback once for each item in the
* provided collection.
*
* It is not necessary to use this class directly; Collection::addIterable
* will automatically create one when it is called.
*/
class TaskForEach extends BaseTask implements NestedCollectionInterface, BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var callable[]
*/
protected $functionStack = [];
/**
* @var callable[]
*/
protected $countingStack = [];
/**
* @var string
*/
protected $message;
/**
* @var array
*/
protected $context = [];
/**
* @var array $iterable
*/
protected $iterable = [];
/**
* @var \Robo\Collection\NestedCollectionInterface
*/
protected $parentCollection;
/**
* @var array $iterable
*/
public function __construct($iterable = [])
{
$this->setIterable($iterable);
}
/**
* @param array $iterable
*
* @return $this
*/
public function setIterable($iterable)
{
$this->iterable = $iterable;
return $this;
}
/**
* @param string $message
* @param array $context
*
* @return $this
*/
public function iterationMessage($message, $context = [])
{
$this->message = $message;
$this->context = $context + ['name' => 'Progress'];
return $this;
}
/**
* @param int|string $key
* @param mixed $value
*/
protected function showIterationMessage($key, $value)
{
if ($this->message) {
$context = ['key' => $key, 'value' => $value];
$context += $this->context;
$context += TaskInfo::getTaskContext($this);
$this->printTaskInfo($this->message, $context);
}
}
/**
* @param callable $fn
*
* @return $this
*/
public function withEachKeyValueCall(callable $fn)
{
$this->functionStack[] = $fn;
return $this;
}
/**
* @param callable $fn
*
* @return $this
*/
public function call(callable $fn)
{
return $this->withEachKeyValueCall(
function ($key, $value) use ($fn) {
return call_user_func($fn, $value);
}
);
}
/**
* @param callable $fn
*
* @return $this
*/
public function withBuilder(callable $fn)
{
$this->countingStack[] =
function ($key, $value) use ($fn) {
// Create a new builder for every iteration
$builder = $this->collectionBuilder();
// The user function should build task operations using
// the $key / $value parameters; we will call run() on
// the builder thus constructed.
call_user_func($fn, $builder, $key, $value);
return $builder->getCollection()->progressIndicatorSteps();
};
return $this->withEachKeyValueCall(
function ($key, $value) use ($fn) {
// Create a new builder for every iteration
$builder = $this->collectionBuilder()
->setParentCollection($this->parentCollection);
// The user function should build task operations using
// the $key / $value parameters; we will call run() on
// the builder thus constructed.
call_user_func($fn, $builder, $key, $value);
return $builder->run();
}
);
}
/**
* {@inheritdoc}
*/
public function setParentCollection(NestedCollectionInterface $parentCollection)
{
$this->parentCollection = $parentCollection;
return $this;
}
/**
* {@inheritdoc}
*/
public function progressIndicatorSteps()
{
$multiplier = count($this->functionStack);
if (!empty($this->countingStack) && count($this->iterable)) {
$value = reset($this->iterable);
$key = key($this->iterable);
foreach ($this->countingStack as $fn) {
$multiplier += call_user_func($fn, $key, $value);
}
}
return count($this->iterable) * $multiplier;
}
/**
* {@inheritdoc}
*/
public function run()
{
$finalResult = Result::success($this);
$this->startProgressIndicator();
foreach ($this->iterable as $key => $value) {
$this->showIterationMessage($key, $value);
try {
foreach ($this->functionStack as $fn) {
$result = call_user_func($fn, $key, $value);
$this->advanceProgressIndicator();
if (!isset($result)) {
$result = Result::success($this);
}
// If the function returns a result, it must either return
// a \Robo\Result or an exit code. In the later case, we
// convert it to a \Robo\Result.
if (!$result instanceof Result) {
$result = new Result($this, $result);
}
if (!$result->wasSuccessful()) {
return $result;
}
$finalResult = $result->merge($finalResult);
}
} catch (\Exception $e) {
return Result::fromException($result, $e);
}
}
$this->stopProgressIndicator();
return $finalResult;
}
}
<?php
namespace Robo;
class TaskInfo
{
/**
* Return a context useful for logging messages.
*
* @param object $task
*
* @return array
*/
public static function getTaskContext($task)
{
return [
'name' => TaskInfo::formatTaskName($task),
'task' => $task,
];
}
/**
* @param object $task
*
* @return string
*/
public static function formatTaskName($task)
{
$name = get_class($task);
$name = preg_replace('~Stack^~', '', $name);
$name = str_replace('Robo\\Task\Base\\', '', $name);
$name = str_replace('Robo\\Task\\', '', $name);
$name = str_replace('Robo\\Collection\\', '', $name);
return $name;
}
}
<?php
namespace Robo\Config;
use Consolidation\Config\Util\ConfigOverlay;
use Consolidation\Config\ConfigInterface;
class Config extends ConfigOverlay implements GlobalOptionDefaultValuesInterface
{
const PROGRESS_BAR_AUTO_DISPLAY_INTERVAL = 'options.progress-delay';
const DEFAULT_PROGRESS_DELAY = 2;
const SIMULATE = 'options.simulate';
// Read-only configuration properties; changing these has no effect.
const INTERACTIVE = 'options.interactive';
const DECORATED = 'options.decorated';
/**
* Create a new configuration object, and initialize it with
* the provided nested array containing configuration data.
*/
public function __construct(array $data = null)
{
parent::__construct();
$this->import($data);
$this->defaults = $this->getGlobalOptionDefaultValues();
}
/**
* {@inheritdoc}
*/
public function import($data)
{
return $this->replace($data);
}
/**
* {@inheritdoc}
*/
public function replace($data)
{
$this->getContext(ConfigOverlay::DEFAULT_CONTEXT)->replace($data);
return $this;
}
/**
* {@inheritdoc}
*/
public function combine($data)
{
$this->getContext(ConfigOverlay::DEFAULT_CONTEXT)->combine($data);
return $this;
}
/**
* Return an associative array containing all of the global configuration
* options and their default values.
*
* @return array
*/
public function getGlobalOptionDefaultValues()
{
$globalOptions =
[
self::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL => self::DEFAULT_PROGRESS_DELAY,
self::SIMULATE => false,
];
return $this->trimPrefixFromGlobalOptions($globalOptions);
}
/**
* Remove the 'options.' prefix from the global options list.
*
* @param array $globalOptions
*
* @return array
*/
protected function trimPrefixFromGlobalOptions($globalOptions)
{
$result = [];
foreach ($globalOptions as $option => $value) {
$option = str_replace('options.', '', $option);
$result[$option] = $value;
}
return $result;
}
/**
* @deprecated Use $config->get(Config::SIMULATE)
*
* @return bool
*/
public function isSimulated()
{
return $this->get(self::SIMULATE);
}
/**
* @deprecated Use $config->set(Config::SIMULATE, true)
*
* @param bool $simulated
*
* @return $this
*/
public function setSimulated($simulated = true)
{
return $this->set(self::SIMULATE, $simulated);
}
/**
* @deprecated Use $config->get(Config::INTERACTIVE)
*
* @return bool
*/
public function isInteractive()
{
return $this->get(self::INTERACTIVE);
}
/**
* @deprecated Use $config->set(Config::INTERACTIVE, true)
*
* @param bool $interactive
*
* @return $this
*/
public function setInteractive($interactive = true)
{
return $this->set(self::INTERACTIVE, $interactive);
}
/**
* @deprecated Use $config->get(Config::DECORATED)
*
* @return bool
*/
public function isDecorated()
{
return $this->get(self::DECORATED);
}
/**
* @deprecated Use $config->set(Config::DECORATED, true)
*
* @param bool $decorated
*
* @return $this
*/
public function setDecorated($decorated = true)
{
return $this->set(self::DECORATED, $decorated);
}
/**
* @deprecated Use $config->set(Config::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL, $interval)
*
* @param int $interval
*
* @return $this
*/
public function setProgressBarAutoDisplayInterval($interval)
{
return $this->set(self::PROGRESS_BAR_AUTO_DISPLAY_INTERVAL, $interval);
}
}
<?php
namespace Robo\Config;
/**
* @deprecated Use robo.yml instead
*
* robo.yml:
*
* options:
* simulated: false
* progress-delay: 2
*
* etc.
*/
interface GlobalOptionDefaultValuesInterface extends \Consolidation\Config\GlobalOptionDefaultValuesInterface
{
}
<?php
namespace Robo;
use Robo\Common\IO;
use Robo\Contract\IOAwareInterface;
use Robo\Contract\BuilderAwareInterface;
use League\Container\ContainerAwareInterface;
use League\Container\ContainerAwareTrait;
class Tasks implements BuilderAwareInterface, IOAwareInterface, ContainerAwareInterface
{
use ContainerAwareTrait;
use LoadAllTasks; // uses TaskAccessor, which uses BuilderAwareTrait
use IO;
/**
* @param bool $stopOnFail
*/
protected function stopOnFail($stopOnFail = true)
{
Result::$stopOnFail = $stopOnFail;
}
}
<?php
namespace Robo;
use Robo\Contract\TaskInterface;
use Robo\Exception\TaskExitException;
use Robo\State\Data;
class Result extends ResultData
{
/**
* @var bool
*/
public static $stopOnFail = false;
/**
* @var \Robo\Contract\TaskInterface
*/
protected $task;
/**
* @param \Robo\Contract\TaskInterface $task
* @param int $exitCode
* @param string $message
* @param array $data
*/
public function __construct(TaskInterface $task, $exitCode, $message = '', $data = [])
{
parent::__construct($exitCode, $message, $data);
$this->task = $task;
$this->printResult();
if (self::$stopOnFail) {
$this->stopOnFail();
}
}
/**
* Tasks should always return a Result. However, they are also
* allowed to return NULL or an array to indicate success.
*
* @param \Robo\Contract\TaskInterface $task
* @param \Robo\Result|\Robo\State\Data|\Robo\ResultData|array|null
*
* @return static
*/
public static function ensureResult($task, $result)
{
if ($result instanceof Result) {
return $result;
}
if (!isset($result)) {
return static::success($task);
}
if ($result instanceof Data) {
return static::success($task, $result->getMessage(), $result->getData());
}
if ($result instanceof ResultData) {
return new Result($task, $result->getExitCode(), $result->getMessage(), $result->getData());
}
if (is_array($result)) {
return static::success($task, '', $result);
}
throw new \Exception(sprintf('Task %s returned a %s instead of a \Robo\Result.', get_class($task), get_class($result)));
}
protected function printResult()
{
// For historic reasons, the Result constructor is responsible
// for printing task results.
// TODO: Make IO the responsibility of some other class. Maintaining
// existing behavior for backwards compatibility. This is undesirable
// in the long run, though, as it can result in unwanted repeated input
// in task collections et. al.
$resultPrinter = Robo::resultPrinter();
if ($resultPrinter) {
if ($resultPrinter->printResult($this)) {
$this->alreadyPrinted();
}
}
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $extension
* @param string $service
*
* @return static
*/
public static function errorMissingExtension(TaskInterface $task, $extension, $service)
{
$messageTpl = 'PHP extension required for %s. Please enable %s';
$message = sprintf($messageTpl, $service, $extension);
return self::error($task, $message);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $class
* @param string $package
*
* @return static
*/
public static function errorMissingPackage(TaskInterface $task, $class, $package)
{
$messageTpl = 'Class %s not found. Please install %s Composer package';
$message = sprintf($messageTpl, $class, $package);
return self::error($task, $message);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $message
* @param array $data
*
* @return static
*/
public static function error(TaskInterface $task, $message, $data = [])
{
return new self($task, self::EXITCODE_ERROR, $message, $data);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param \Exception $e
* @param array $data
*
* @return static
*/
public static function fromException(TaskInterface $task, \Exception $e, $data = [])
{
$exitCode = $e->getCode();
if (!$exitCode) {
$exitCode = self::EXITCODE_ERROR;
}
return new self($task, $exitCode, $e->getMessage(), $data);
}
/**
* @param \Robo\Contract\TaskInterface $task
* @param string $message
* @param array $data
*
* @return static
*/
public static function success(TaskInterface $task, $message = '', $data = [])
{
return new self($task, self::EXITCODE_OK, $message, $data);
}
/**
* Return a context useful for logging messages.
*
* @return array
*/
public function getContext()
{
$task = $this->getTask();
return TaskInfo::getTaskContext($task) + [
'code' => $this->getExitCode(),
'data' => $this->getArrayCopy(),
'time' => $this->getExecutionTime(),
'message' => $this->getMessage(),
];
}
/**
* Add the results from the most recent task to the accumulated
* results from all tasks that have run so far, merging data
* as necessary.
*
* @param int|string $key
* @param \Robo\Result $taskResult
*/
public function accumulate($key, Result $taskResult)
{
// If the task is unnamed, then all of its data elements
// just get merged in at the top-level of the final Result object.
if (static::isUnnamed($key)) {
$this->merge($taskResult);
} elseif (isset($this[$key])) {
// There can only be one task with a given name; however, if
// there are tasks added 'before' or 'after' the named task,
// then the results from these will be stored under the same
// name unless they are given a name of their own when added.
$current = $this[$key];
$this[$key] = $taskResult->merge($current);
} else {
$this[$key] = $taskResult;
}
}
/**
* We assume that named values (e.g. for associative array keys)
* are non-numeric; numeric keys are presumed to simply be the
* index of an array, and therefore insignificant.
*
* @param int|string $key
*
* @return bool
*/
public static function isUnnamed($key)
{
return is_numeric($key);
}
/**
* @return \Robo\Contract\TaskInterface
*/
public function getTask()
{
return $this->task;
}
/**
* @return \Robo\Contract\TaskInterface
*/
public function cloneTask()
{
$reflect = new \ReflectionClass(get_class($this->task));
return $reflect->newInstanceArgs(func_get_args());
}
/**
* @return bool
*
* @deprecated since 1.0.
*
* @see wasSuccessful()
*/
public function __invoke()
{
trigger_error(__METHOD__ . ' is deprecated: use wasSuccessful() instead.', E_USER_DEPRECATED);
return $this->wasSuccessful();
}
/**
* @return $this
*/
public function stopOnFail()
{
if (!$this->wasSuccessful()) {
$resultPrinter = Robo::resultPrinter();
if ($resultPrinter) {
$resultPrinter->printStopOnFail($this);
}
$this->exitEarly($this->getExitCode());
}
return $this;
}
/**
* @param int $status
*
* @throws \Robo\Exception\TaskExitException
*/
private function exitEarly($status)
{
throw new TaskExitException($this->getTask(), $this->getMessage(), $status);
}
}
<?php
namespace Robo;
use Composer\Autoload\ClassLoader;
use League\Container\Container;
use League\Container\ContainerInterface;
use Robo\Common\ProcessExecutor;
use Consolidation\Config\ConfigInterface;
use Consolidation\Config\Loader\ConfigProcessor;
use Consolidation\Config\Loader\YamlConfigLoader;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Process\Process;
/**
* Manages the container reference and other static data. Favor
* using dependency injection wherever possible. Avoid using
* this class directly, unless setting up a custom DI container.
*/
class Robo
{
const APPLICATION_NAME = 'Robo';
const VERSION = '2.0.2';
/**
* The currently active container object, or NULL if not initialized yet.
*
* @var \League\Container\ContainerInterface|null
*/
protected static $container;
/**
* Entrypoint for standalone Robo-based tools. See docs/framework.md.
*
* @param string[] $argv
* @param string $commandClasses
* @param null|string $appName
* @param null|string $appVersion
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
* @param null|string $repository
*
* @return int
*/
public static function run($argv, $commandClasses, $appName = null, $appVersion = null, $output = null, $repository = null)
{
$runner = new \Robo\Runner($commandClasses);
$runner->setSelfUpdateRepository($repository);
$statusCode = $runner->execute($argv, $appName, $appVersion, $output);
return $statusCode;
}
/**
* Sets a new global container.
*
* @param \League\Container\ContainerInterface $container
* A new container instance to replace the current.
*/
public static function setContainer(ContainerInterface $container)
{
static::$container = $container;
}
/**
* Unsets the global container.
*/
public static function unsetContainer()
{
static::$container = null;
}
/**
* Returns the currently active global container.
*
* @return \League\Container\ContainerInterface
*
* @throws \RuntimeException
*/
public static function getContainer()
{
if (static::$container === null) {
throw new \RuntimeException('container is not initialized yet. \Robo\Robo::setContainer() must be called with a real container.');
}
return static::$container;
}
/**
* Returns TRUE if the container has been initialized, FALSE otherwise.
*
* @return bool
*/
public static function hasContainer()
{
return static::$container !== null;
}
/**
* Create a config object and load it from the provided paths.
*
* @param string[] $paths
*
* @return \Consolidation\Config\ConfigInterface
*/
public static function createConfiguration($paths)
{
$config = new \Robo\Config\Config();
static::loadConfiguration($paths, $config);
return $config;
}
/**
* Use a simple config loader to load configuration values from specified paths
*
* @param string[] $paths
* @param null|\Consolidation\Config\ConfigInterface $config
*/
public static function loadConfiguration($paths, $config = null)
{
if ($config == null) {
$config = static::config();
}
$loader = new YamlConfigLoader();
$processor = new ConfigProcessor();
$processor->add($config->export());
foreach ($paths as $path) {
$processor->extend($loader->load($path));
}
$config->import($processor->export());
}
/**
* Create a container and initiailze it. If you wish to *change*
* anything defined in the container, then you should call
* \Robo::configureContainer() instead of this function.
*
* @param null|\Symfony\Component\Console\Input\InputInterface $input
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
* @param null|\Robo\Application $app
* @param null|\Consolidation\Config\ConfigInterface $config
* @param null|\Composer\Autoload\ClassLoader $classLoader
*
* @return \League\Container\Container|\League\Container\ContainerInterface
*/
public static function createDefaultContainer($input = null, $output = null, $app = null, $config = null, $classLoader = null)
{
// Do not allow this function to be called more than once.
if (static::hasContainer()) {
return static::getContainer();
}
if (!$app) {
$app = static::createDefaultApplication();
}
if (!$config) {
$config = new \Robo\Config\Config();
}
// Set up our dependency injection container.
$container = new Container();
static::configureContainer($container, $app, $config, $input, $output, $classLoader);
// Set the application dispatcher
$app->setDispatcher($container->get('eventDispatcher'));
return $container;
}
/**
* Initialize a container with all of the default Robo services.
* IMPORTANT: after calling this method, clients MUST call:
*
* $dispatcher = $container->get('eventDispatcher');
* $app->setDispatcher($dispatcher);
*
* Any modification to the container should be done prior to fetching
* objects from it.
*
* It is recommended to use \Robo::createDefaultContainer()
* instead, which does all required setup for the caller, but has
* the limitation that the container it creates can only be
* extended, not modified.
*
* @param \League\Container\ContainerInterface $container
* @param \Symfony\Component\Console\Application $app
* @param \Consolidation\Config\ConfigInterface $config
* @param null|\Symfony\Component\Console\Input\InputInterface $input
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
* @param null|\Composer\Autoload\ClassLoader $classLoader
*/
public static function configureContainer(ContainerInterface $container, SymfonyApplication $app, ConfigInterface $config, $input = null, $output = null, $classLoader = null)
{
// Self-referential container refernce for the inflector
$container->add('container', $container);
static::setContainer($container);
// Create default input and output objects if they were not provided
if (!$input) {
$input = new StringInput('');
}
if (!$output) {
$output = new \Symfony\Component\Console\Output\ConsoleOutput();
}
if (!$classLoader) {
$classLoader = new ClassLoader();
}
$config->set(Config::DECORATED, $output->isDecorated());
$config->set(Config::INTERACTIVE, $input->isInteractive());
$container->share('application', $app);
$container->share('config', $config);
$container->share('input', $input);
$container->share('output', $output);
$container->share('outputAdapter', \Robo\Common\OutputAdapter::class);
$container->share('classLoader', $classLoader);
// Register logging and related services.
$container->share('logStyler', \Robo\Log\RoboLogStyle::class);
$container->share('logger', \Robo\Log\RoboLogger::class)
->withArgument('output')
->withMethodCall('setLogOutputStyler', ['logStyler']);
$container->add('progressBar', \Symfony\Component\Console\Helper\ProgressBar::class)
->withArgument('output');
$container->share('progressIndicator', \Robo\Common\ProgressIndicator::class)
->withArgument('progressBar')
->withArgument('output');
$container->share('resultPrinter', \Robo\Log\ResultPrinter::class);
$container->add('simulator', \Robo\Task\Simulator::class);
$container->share('globalOptionsEventListener', \Robo\GlobalOptionsEventListener::class)
->withMethodCall('setApplication', ['application']);
$container->share('injectConfigEventListener', \Consolidation\Config\Inject\ConfigForCommand::class)
->withArgument('config')
->withMethodCall('setApplication', ['application']);
$container->share('collectionProcessHook', \Robo\Collection\CollectionProcessHook::class);
$container->share('alterOptionsCommandEvent', \Consolidation\AnnotatedCommand\Options\AlterOptionsCommandEvent::class)
->withArgument('application');
$container->share('hookManager', \Consolidation\AnnotatedCommand\Hooks\HookManager::class)
->withMethodCall('addCommandEvent', ['alterOptionsCommandEvent'])
->withMethodCall('addCommandEvent', ['injectConfigEventListener'])
->withMethodCall('addCommandEvent', ['globalOptionsEventListener'])
->withMethodCall('addResultProcessor', ['collectionProcessHook', '*']);
$container->share('eventDispatcher', \Symfony\Component\EventDispatcher\EventDispatcher::class)
->withMethodCall('addSubscriber', ['hookManager']);
$container->share('formatterManager', \Consolidation\OutputFormatters\FormatterManager::class)
->withMethodCall('addDefaultFormatters', [])
->withMethodCall('addDefaultSimplifiers', []);
$container->share('prepareTerminalWidthOption', \Consolidation\AnnotatedCommand\Options\PrepareTerminalWidthOption::class)
->withMethodCall('setApplication', ['application']);
$container->share('symfonyStyleInjector', \Robo\Symfony\SymfonyStyleInjector::class);
$container->share('parameterInjection', \Consolidation\AnnotatedCommand\ParameterInjection::class)
->withMethodCall('register', ['Symfony\Component\Console\Style\SymfonyStyle', 'symfonyStyleInjector']);
$container->share('commandProcessor', \Consolidation\AnnotatedCommand\CommandProcessor::class)
->withArgument('hookManager')
->withMethodCall('setFormatterManager', ['formatterManager'])
->withMethodCall('addPrepareFormatter', ['prepareTerminalWidthOption'])
->withMethodCall('setParameterInjection', ['parameterInjection'])
->withMethodCall(
'setDisplayErrorFunction',
[
function ($output, $message) use ($container) {
$logger = $container->get('logger');
$logger->error($message);
}
]
);
$container->share('stdinHandler', \Consolidation\AnnotatedCommand\Input\StdinHandler::class);
$container->share('commandFactory', \Consolidation\AnnotatedCommand\AnnotatedCommandFactory::class)
->withMethodCall('setCommandProcessor', ['commandProcessor']);
$container->share('relativeNamespaceDiscovery', \Robo\ClassDiscovery\RelativeNamespaceDiscovery::class)
->withArgument('classLoader');
// Deprecated: favor using collection builders to direct use of collections.
$container->add('collection', \Robo\Collection\Collection::class);
// Deprecated: use CollectionBuilder::create() instead -- or, better
// yet, BuilderAwareInterface::collectionBuilder() if available.
$container->add('collectionBuilder', \Robo\Collection\CollectionBuilder::class);
static::addInflectors($container);
// Make sure the application is appropriately initialized.
$app->setAutoExit(false);
}
/**
* @param null|string $appName
* @param null|string $appVersion
*
* @return \Robo\Application
*/
public static function createDefaultApplication($appName = null, $appVersion = null)
{
$appName = $appName ?: self::APPLICATION_NAME;
$appVersion = $appVersion ?: self::VERSION;
$app = new \Robo\Application($appName, $appVersion);
$app->setAutoExit(false);
return $app;
}
/**
* Add the Robo League\Container inflectors to the container
*
* @param \League\Container\ContainerInterface $container
*/
public static function addInflectors($container)
{
// Register our various inflectors.
$container->inflector(\Robo\Contract\ConfigAwareInterface::class)
->invokeMethod('setConfig', ['config']);
$container->inflector(\Psr\Log\LoggerAwareInterface::class)
->invokeMethod('setLogger', ['logger']);
$container->inflector(\League\Container\ContainerAwareInterface::class)
->invokeMethod('setContainer', ['container']);
$container->inflector(\Symfony\Component\Console\Input\InputAwareInterface::class)
->invokeMethod('setInput', ['input']);
$container->inflector(\Robo\Contract\OutputAwareInterface::class)
->invokeMethod('setOutput', ['output']);
$container->inflector(\Robo\Contract\ProgressIndicatorAwareInterface::class)
->invokeMethod('setProgressIndicator', ['progressIndicator']);
$container->inflector(\Consolidation\AnnotatedCommand\Events\CustomEventAwareInterface::class)
->invokeMethod('setHookManager', ['hookManager']);
$container->inflector(\Robo\Contract\VerbosityThresholdInterface::class)
->invokeMethod('setOutputAdapter', ['outputAdapter']);
$container->inflector(\Consolidation\AnnotatedCommand\Input\StdinAwareInterface::class)
->invokeMethod('setStdinHandler', ['stdinHandler']);
}
/**
* Retrieves a service from the container.
*
* Use this method if the desired service is not one of those with a dedicated
* accessor method below. If it is listed below, those methods are preferred
* as they can return useful type hints.
*
* @param string $id
* The ID of the service to retrieve.
*
* @return mixed
* The specified service.
*/
public static function service($id)
{
return static::getContainer()->get($id);
}
/**
* Indicates if a service is defined in the container.
*
* @param string $id
* The ID of the service to check.
*
* @return bool
* TRUE if the specified service exists, FALSE otherwise.
*/
public static function hasService($id)
{
// Check hasContainer() first in order to always return a Boolean.
return static::hasContainer() && static::getContainer()->has($id);
}
/**
* Return the result printer object.
*
* @return \Robo\Log\ResultPrinter
*/
public static function resultPrinter()
{
return static::service('resultPrinter');
}
/**
* @return \Consolidation\Config\ConfigInterface
*/
public static function config()
{
return static::service('config');
}
/**
* @return \Consolidation\Log\Logger
*/
public static function logger()
{
return static::service('logger');
}
/**
* @return \Robo\Application
*/
public static function application()
{
return static::service('application');
}
/**
* Return the output object.
*
* @return \Symfony\Component\Console\Output\OutputInterface
*/
public static function output()
{
return static::service('output');
}
/**
* Return the input object.
*
* @return \Symfony\Component\Console\Input\InputInterface
*/
public static function input()
{
return static::service('input');
}
/**
* @return \Robo\Common\ProcessExecutor
*/
public static function process(Process $process)
{
return ProcessExecutor::create(static::getContainer(), $process);
}
}
<?php
namespace Robo\State;
use Robo\State\Data;
interface StateAwareInterface
{
/**
* @return \Robo\State\Data
*/
public function getState();
/**
* @param \Robo\State\Data $state
*/
public function setState(Data $state);
/**
* @param int|string $key
* @param mixed $value
*/
public function setStateValue($key, $value);
/**
* @param \Robo\State\Data
* Update state takes precedence over current state.
*/
public function updateState(Data $update);
public function resetState();
}
<?php
namespace Robo\State;
use Robo\State\Data;
interface Consumer
{
/**
* @return \Robo\State\Data
*/
public function receiveState(Data $state);
}
<?php
namespace Robo\State;
use Robo\State\Data;
/**
* @see \Robo\State\StateAwareInterface
*/
trait StateAwareTrait
{
/**
* @var \Robo\State\Data
*/
protected $state;
/**
* @return \Robo\State\Data
*/
public function getState()
{
return $this->state;
}
public function setState(Data $state)
{
$this->state = $state;
}
/**
* @param int|string $key
* @param mixed $value
*/
public function setStateValue($key, $value)
{
$this->state[$key] = $value;
}
public function updateState(Data $update)
{
$this->state->update($update);
}
public function resetState()
{
$this->state = new Data();
}
}
<?php
namespace Robo\State;
/**
* A State\Data object contains a "message" (the primary result) and a
* data array (the persistent state). The message is transient, and does
* not move into the persistent state unless explicitly copied there.
*/
class Data extends \ArrayObject
{
/**
* @var string
*/
protected $message;
/**
* @param string $message
* @param array $data
*/
public function __construct($message = '', $data = [])
{
$this->message = $message;
parent::__construct($data);
}
/**
* @return array
*/
public function getData()
{
return $this->getArrayCopy();
}
/**
* @return string
*/
public function getMessage()
{
return $this->message;
}
/**
* @param string $message
*/
public function setMessage($message)
{
$this->message = $message;
}
/**
* Merge another result into this result. Data already
* existing in this result takes precedence over the
* data in the Result being merged.
*
* @param \Robo\State\Data $result
*
* @return $this
*/
public function merge(Data $result)
{
$mergedData = $this->getArrayCopy() + $result->getArrayCopy();
$this->exchangeArray($mergedData);
return $this;
}
/**
* Update the current data with the data provided in the parameter.
* Provided data takes precedence.
*
* @param \ArrayObject $update
*
* @return $this
*/
public function update(\ArrayObject $update)
{
$iterator = $update->getIterator();
while ($iterator->valid()) {
$this[$iterator->key()] = $iterator->current();
$iterator->next();
}
return $this;
}
/**
* Merge another result into this result. Data already
* existing in this result takes precedence over the
* data in the Result being merged.
*
* $data['message'] is handled specially, and is appended
* to $this->message if set.
*
* @param array $data
*
* @return array
*/
public function mergeData(array $data)
{
$mergedData = $this->getArrayCopy() + $data;
$this->exchangeArray($mergedData);
return $mergedData;
}
/**
* @return bool
*/
public function hasExecutionTime()
{
return isset($this['time']);
}
/**
* @return null|float
*/
public function getExecutionTime()
{
if (!$this->hasExecutionTime()) {
return null;
}
return $this['time'];
}
/**
* Accumulate execution time
*
* @param array|float $duration
*
* @return null|float
*/
public function accumulateExecutionTime($duration)
{
// Convert data arrays to scalar
if (is_array($duration)) {
$duration = isset($duration['time']) ? $duration['time'] : 0;
}
$this['time'] = $this->getExecutionTime() + $duration;
return $this->getExecutionTime();
}
/**
* Accumulate the message.
*
* @param string $message
*
* @return string
*/
public function accumulateMessage($message)
{
if (!empty($this->message)) {
$this->message .= "\n";
}
$this->message .= $message;
return $this->getMessage();
}
}
<?php
namespace Robo;
/**
* @deprecated Use \Robo\Config\Config
*/
class Config extends \Robo\Config\Config
{
}
<?php
namespace Robo\Common;
class TimeKeeper
{
const MINUTE = 60;
const HOUR = 3600;
const DAY = 86400;
/**
* @var float
*/
protected $startedAt;
/**
* @var float
*/
protected $finishedAt;
public function start()
{
if ($this->startedAt) {
return;
}
// Get time in seconds as a float, accurate to the microsecond.
$this->startedAt = microtime(true);
}
public function stop()
{
$this->finishedAt = microtime(true);
}
/**
* @return float|null
*/
public function elapsed()
{
$finished = $this->finishedAt ? $this->finishedAt : microtime(true);
if ($finished - $this->startedAt <= 0) {
return null;
}
return $finished - $this->startedAt;
}
/**
* Format a duration into a human-readable time.
*
* @param float $duration
* Duration in seconds, with fractional component.
*
* @return string
*/
public static function formatDuration($duration)
{
if ($duration >= self::DAY * 2) {
return gmdate('z \d\a\y\s H:i:s', $duration);
}
if ($duration > self::DAY) {
return gmdate('\1 \d\a\y H:i:s', $duration);
}
if ($duration > self::HOUR) {
return gmdate("H:i:s", $duration);
}
if ($duration > self::MINUTE) {
return gmdate("i:s", $duration);
}
return round($duration, 3) . 's';
}
}
<?php
namespace Robo\Common;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
trait OutputAwareTrait
{
/**
* @var \Symfony\Component\Console\Output\OutputInterface
*/
protected $output;
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @return $this
*
* @see \Robo\Contract\OutputAwareInterface::setOutput()
*/
public function setOutput(OutputInterface $output)
{
$this->output = $output;
return $this;
}
/**
* @return \Symfony\Component\Console\Output\OutputInterface
*/
protected function output()
{
if (!isset($this->output)) {
$this->setOutput(new NullOutput());
}
return $this->output;
}
/**
* @return \Symfony\Component\Console\Output\OutputInterface
*/
protected function stderr()
{
$output = $this->output();
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
return $output;
}
/**
* Backwards compatibility
*
* @return \Symfony\Component\Console\Output\OutputInterface
*
* @deprecated
*/
protected function getOutput()
{
return $this->output();
}
}
<?php
namespace Robo\Common;
use Robo\Contract\InflectionInterface;
trait InflectionTrait
{
/**
* Ask the provided parent class to inject all of the dependencies
* that it has and we need.
*
* @param \Robo\Contract\InflectionInterface $parent
*
* @return $this
*/
public function inflect(InflectionInterface $parent)
{
$parent->injectDependencies($this);
return $this;
}
}
<?php
namespace Robo\Common;
use Robo\Robo;
use Consolidation\Config\ConfigInterface;
trait ConfigAwareTrait
{
/**
* @var \Consolidation\Config\ConfigInterface
*/
protected $config;
/**
* Set the config management object.
*
* @param \Consolidation\Config\ConfigInterface $config
*
* @return $this
*/
public function setConfig(ConfigInterface $config)
{
$this->config = $config;
return $this;
}
/**
* Get the config management object.
*
* @return \Consolidation\Config\ConfigInterface
*/
public function getConfig()
{
return $this->config;
}
/**
* Any class that uses ConfigAwareTrait SHOULD override this method
* , and define a prefix for its configuration items. This is usually
* done in a base class. When used, this method should return a string
* that ends with a "."; see BaseTask::configPrefix().
*
* @return string
*/
protected static function configPrefix()
{
return '';
}
protected static function configClassIdentifier($classname)
{
$configIdentifier = strtr($classname, '\\', '.');
$configIdentifier = preg_replace('#^(.*\.Task\.|\.)#', '', $configIdentifier);
return $configIdentifier;
}
protected static function configPostfix()
{
return '';
}
/**
* @param string $key
*
* @return string
*/
private static function getClassKey($key)
{
$configPrefix = static::configPrefix(); // task.
$configClass = static::configClassIdentifier(get_called_class()); // PARTIAL_NAMESPACE.CLASSNAME
$configPostFix = static::configPostfix(); // .settings
return sprintf('%s%s%s.%s', $configPrefix, $configClass, $configPostFix, $key);
}
/**
* @param string $key
* @param mixed $value
* @param \Consolidation\Config\ConfigInterface|null $config
*/
public static function configure($key, $value, $config = null)
{
if (!$config) {
$config = Robo::config();
}
$config->setDefault(static::getClassKey($key), $value);
}
/**
* @param string $key
* @param mixed|null $default
*
* @return mixed|null
*/
protected function getConfigValue($key, $default = null)
{
if (!$this->getConfig()) {
return $default;
}
return $this->getConfig()->get(static::getClassKey($key), $default);
}
}
<?php
namespace Robo\Common;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
trait IO
{
use InputAwareTrait;
use OutputAwareTrait;
/**
* @var \Symfony\Component\Console\Style\SymfonyStyle
*/
protected $io;
/**
* Provide access to SymfonyStyle object.
*
* @deprecated Use a style injector instead
*
* @return \Symfony\Component\Console\Style\SymfonyStyle
*
* @see http://symfony.com/blog/new-in-symfony-2-8-console-style-guide
*/
protected function io()
{
if (!$this->io) {
$this->io = new SymfonyStyle($this->input(), $this->output());
}
return $this->io;
}
/**
* @param string $nonDecorated
* @param string $decorated
*
* @return string
*/
protected function decorationCharacter($nonDecorated, $decorated)
{
if (!$this->output()->isDecorated() || (strncasecmp(PHP_OS, 'WIN', 3) == 0)) {
return $nonDecorated;
}
return $decorated;
}
/**
* @param string $text
*/
protected function say($text)
{
$char = $this->decorationCharacter('>', '➜');
$this->writeln("$char $text");
}
/**
* @param string $text
* @param int $length
* @param string $color
*/
protected function yell($text, $length = 40, $color = 'green')
{
$char = $this->decorationCharacter(' ', '➜');
$format = "$char <fg=white;bg=$color;options=bold>%s</fg=white;bg=$color;options=bold>";
$this->formattedOutput($text, $length, $format);
}
/**
* @param string $text
* @param int $length
* @param string $format
*/
protected function formattedOutput($text, $length, $format)
{
$lines = explode("\n", trim($text, "\n"));
$maxLineLength = array_reduce(array_map('strlen', $lines), 'max');
$length = max($length, $maxLineLength);
$len = $length + 2;
$space = str_repeat(' ', $len);
$this->writeln(sprintf($format, $space));
foreach ($lines as $line) {
$line = str_pad($line, $length, ' ', STR_PAD_BOTH);
$this->writeln(sprintf($format, " $line "));
}
$this->writeln(sprintf($format, $space));
}
/**
* @param string $question
* @param bool $hideAnswer
*
* @return string
*/
protected function ask($question, $hideAnswer = false)
{
if ($hideAnswer) {
return $this->askHidden($question);
}
return $this->doAsk(new Question($this->formatQuestion($question)));
}
/**
* @param string $question
*
* @return string
*/
protected function askHidden($question)
{
$question = new Question($this->formatQuestion($question));
$question->setHidden(true);
return $this->doAsk($question);
}
/**
* @param string $question
* @param string $default
*
* @return string
*/
protected function askDefault($question, $default)
{
return $this->doAsk(new Question($this->formatQuestion("$question [$default]"), $default));
}
/**
* @param string $question
* @param bool $default
*
* @return string
*/
protected function confirm($question, $default = false)
{
return $this->doAsk(new ConfirmationQuestion($this->formatQuestion($question . ' (y/n)'), $default));
}
/**
* @param \Symfony\Component\Console\Question\Question $question
*
* @return string
*/
protected function doAsk(Question $question)
{
return $this->getDialog()->ask($this->input(), $this->output(), $question);
}
/**
* @param string $message
*
* @return string
*/
protected function formatQuestion($message)
{
return "<question>? $message</question> ";
}
/**
* @return \Symfony\Component\Console\Helper\QuestionHelper
*/
protected function getDialog()
{
return new QuestionHelper();
}
/**
* @param $text
*/
protected function writeln($text)
{
$this->output()->writeln($text);
}
}
<?php
namespace Robo\Common;
/**
* Simplifies generating of configuration chanined methods.
* You can only define configuration properties and use magic methods to set them.
* Methods will be named the same way as properties.
* * Boolean properties are switched on/off if no values is provided.
* * Array properties can accept non-array values, in this case value will be appended to array.
* You should also define phpdoc for methods.
*/
trait DynamicParams
{
/**
* @param string $property
* @param array $args
*
* @return $this
*/
public function __call($property, $args)
{
if (!property_exists($this, $property)) {
throw new \RuntimeException("Property $property in task " . get_class($this) . ' does not exists');
}
// toggle boolean values
if (!isset($args[0]) and (is_bool($this->$property))) {
$this->$property = !$this->$property;
return $this;
}
// append item to array
if (is_array($this->$property)) {
if (is_array($args[0])) {
$this->$property = $args[0];
} else {
array_push($this->$property, $args[0]);
}
return $this;
}
$this->$property = $args[0];
return $this;
}
}
<?php
namespace Robo\Common;
use Robo\ResultData;
use Symfony\Component\Process\Process;
/**
* Class ExecTrait
* @package Robo\Common
*/
trait ExecTrait
{
/**
* @var bool
*/
protected $background = false;
/**
* @var null|int
*/
protected $timeout = null;
/**
* @var null|int
*/
protected $idleTimeout = null;
/**
* @var null|array
*/
protected $env = null;
/**
* @var Process
*/
protected $process;
/**
* @var resource|string
*/
protected $input;
/**
* @var boolean
*/
protected $interactive = null;
/**
* @var bool
*/
protected $isPrinted = true;
/**
* @var bool
*/
protected $isMetadataPrinted = true;
/**
* @var string
*/
protected $workingDirectory;
/**
* @return string
*/
abstract public function getCommandDescription();
/**
* @see \Robo\Common\ProgressIndicatorAwareTrait
* @see \Robo\Common\Timer
*/
abstract protected function startTimer();
/**
* @see \Robo\Common\ProgressIndicatorAwareTrait
* @see \Robo\Common\Timer
*/
abstract protected function stopTimer();
/**
* @return null|float
*
* @see \Robo\Common\ProgressIndicatorAwareTrait
* @see \Robo\Common\Timer
*/
abstract protected function getExecutionTime();
/**
* @return bool
*
* @see \Robo\Common\TaskIO
*/
abstract protected function hideTaskProgress();
/**
* @param bool $inProgress
*
* @see \Robo\Common\TaskIO
*/
abstract protected function showTaskProgress($inProgress);
/**
* @param string $text
* @param null|array $context
*
* @see \Robo\Common\TaskIO
*/
abstract protected function printTaskInfo($text, $context = null);
/**
* @return bool
*
* @see \Robo\Common\VerbosityThresholdTrait
*/
abstract public function verbosityMeetsThreshold();
/**
* @param string $message
*
* @see \Robo\Common\VerbosityThresholdTrait
*/
abstract public function writeMessage($message);
/**
* Sets $this->interactive() based on posix_isatty().
*
* @return $this
*/
public function detectInteractive()
{
// If the caller did not explicity set the 'interactive' mode,
// and output should be produced by this task (verbosityMeetsThreshold),
// then we will automatically set interactive mode based on whether
// or not output was redirected when robo was executed.
if (!isset($this->interactive) && function_exists('posix_isatty') && $this->verbosityMeetsThreshold()) {
$this->interactive = posix_isatty(STDOUT);
}
return $this;
}
/**
* Executes command in background mode (asynchronously)
*
* @param bool $arg
*
* @return $this
*/
public function background($arg = true)
{
$this->background = $arg;
return $this;
}
/**
* Stop command if it runs longer then $timeout in seconds
*
* @param int $timeout
*
* @return $this
*/
public function timeout($timeout)
{
$this->timeout = $timeout;
return $this;
}
/**
* Stops command if it does not output something for a while
*
* @param int $timeout
*
* @return $this
*/
public function idleTimeout($timeout)
{
$this->idleTimeout = $timeout;
return $this;
}
/**
* Set a single environment variable, or multiple.
*
* @param string|array $env
* @param bool|string $value
*
* @return $this
*/
public function env($env, $value = null)
{
if (!is_array($env)) {
$env = [$env => ($value ? $value : true)];
}
return $this->envVars($env);
}
/**
* Sets the environment variables for the command
*
* @param array $env
*
* @return $this
*/
public function envVars(array $env)
{
$this->env = $this->env ? $env + $this->env : $env;
return $this;
}
/**
* Pass an input to the process. Can be resource created with fopen() or string
*
* @param resource|string $input
*
* @return $this
*/
public function setInput($input)
{
$this->input = $input;
return $this;
}
/**
* Attach tty to process for interactive input
*
* @param bool $interactive
*
* @return $this
*/
public function interactive($interactive = true)
{
$this->interactive = $interactive;
return $this;
}
/**
* Is command printing its output to screen
*
* @return bool
*/
public function getPrinted()
{
return $this->isPrinted;
}
/**
* Changes working directory of command
*
* @param string $dir
*
* @return $this
*/
public function dir($dir)
{
$this->workingDirectory = $dir;
return $this;
}
/**
* Shortcut for setting isPrinted() and isMetadataPrinted() to false.
*
* @param bool $arg
*
* @return $this
*/
public function silent($arg)
{
if (is_bool($arg)) {
$this->isPrinted = !$arg;
$this->isMetadataPrinted = !$arg;
}
return $this;
}
/**
* Should command output be printed
*
* @param bool $arg
*
* @return $this
*
* @deprecated
*/
public function printed($arg)
{
$this->logger->warning("printed() is deprecated. Please use printOutput().");
return $this->printOutput($arg);
}
/**
* Should command output be printed
*
* @param bool $arg
*
* @return $this
*/
public function printOutput($arg)
{
if (is_bool($arg)) {
$this->isPrinted = $arg;
}
return $this;
}
/**
* Should command metadata be printed. I,e., command and timer.
*
* @param bool $arg
*
* @return $this
*/
public function printMetadata($arg)
{
if (is_bool($arg)) {
$this->isMetadataPrinted = $arg;
}
return $this;
}
/**
* @param \Symfony\Component\Process\Process $process
* @param callable $output_callback
*
* @return \Robo\ResultData
*/
protected function execute($process, $output_callback = null)
{
$this->process = $process;
if (!$output_callback) {
$output_callback = function ($type, $buffer) {
$progressWasVisible = $this->hideTaskProgress();
$this->writeMessage($buffer);
$this->showTaskProgress($progressWasVisible);
};
}
$this->detectInteractive();
if ($this->isMetadataPrinted) {
$this->printAction();
}
$this->process->setTimeout($this->timeout);
$this->process->setIdleTimeout($this->idleTimeout);
if ($this->workingDirectory) {
$this->process->setWorkingDirectory($this->workingDirectory);
}
if ($this->input) {
$this->process->setInput($this->input);
}
if ($this->interactive && $this->isPrinted) {
$this->process->setTty(true);
}
if (isset($this->env)) {
// Symfony 4 will inherit environment variables by default, but until
// then, manually ensure they are inherited.
if (method_exists($this->process, 'inheritEnvironmentVariables')) {
$this->process->inheritEnvironmentVariables();
}
$this->process->setEnv($this->env);
}
if (!$this->background && !$this->isPrinted) {
$this->startTimer();
$this->process->run();
$this->stopTimer();
$output = rtrim($this->process->getOutput());
return new ResultData(
$this->process->getExitCode(),
$output,
$this->getResultData()
);
}
if (!$this->background && $this->isPrinted) {
$this->startTimer();
$this->process->run($output_callback);
$this->stopTimer();
return new ResultData(
$this->process->getExitCode(),
$this->process->getOutput(),
$this->getResultData()
);
}
try {
$this->process->start();
} catch (\Exception $e) {
return new ResultData(
$this->process->getExitCode(),
$e->getMessage(),
$this->getResultData()
);
}
return new ResultData($this->process->getExitCode());
}
protected function stop()
{
if ($this->background && isset($this->process) && $this->process->isRunning()) {
$this->process->stop();
$this->printTaskInfo(
"Stopped {command}",
['command' => $this->getCommandDescription()]
);
}
}
/**
* @param array $context
*/
protected function printAction($context = [])
{
$command = $this->getCommandDescription();
$formatted_command = $this->formatCommandDisplay($command);
$dir = $this->workingDirectory ? " in {dir}" : "";
$this->printTaskInfo("Running {command}$dir", [
'command' => $formatted_command,
'dir' => $this->workingDirectory
] + $context);
}
/**
* @param string $command
*
* @return string
*/
protected function formatCommandDisplay($command)
{
$formatted_command = str_replace("&&", "&&\n", $command);
$formatted_command = str_replace("||", "||\n", $formatted_command);
return $formatted_command;
}
/**
* Gets the data array to be passed to Result().
*
* @return array
* The data array passed to Result().
*/
protected function getResultData()
{
if ($this->isMetadataPrinted) {
return ['time' => $this->getExecutionTime()];
}
return [];
}
}
<?php
namespace Robo\Common;
use Robo\Collection\CollectionBuilder;
trait BuilderAwareTrait
{
/**
* @var \Robo\Collection\CollectionBuilder
*/
protected $builder;
/**
* @see \Robo\Contract\BuilderAwareInterface::setBuilder()
*
* @param \Robo\Collection\CollectionBuilder $builder
*
* @return $this
*/
public function setBuilder(CollectionBuilder $builder)
{
$this->builder = $builder;
return $this;
}
/**
* @see \Robo\Contract\BuilderAwareInterface::getBuilder()
*
* @return \Robo\Collection\CollectionBuilder
*/
public function getBuilder()
{
return $this->builder;
}
/**
* @return \Robo\Collection\CollectionBuilder
*/
protected function collectionBuilder()
{
return $this->getBuilder()->newBuilder();
}
}
<?php
namespace Robo\Common;
use Robo\Contract\OutputAdapterInterface;
use Robo\Contract\OutputAwareInterface;
use Robo\Contract\VerbosityThresholdInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Adapt OutputInterface or other output function to the VerbosityThresholdInterface.
*/
class OutputAdapter implements OutputAdapterInterface, OutputAwareInterface
{
use OutputAwareTrait;
/**
* @var int[]
*/
protected $verbosityMap = [
VerbosityThresholdInterface::VERBOSITY_NORMAL => OutputInterface::VERBOSITY_NORMAL,
VerbosityThresholdInterface::VERBOSITY_VERBOSE => OutputInterface::VERBOSITY_VERBOSE,
VerbosityThresholdInterface::VERBOSITY_VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE,
VerbosityThresholdInterface::VERBOSITY_DEBUG => OutputInterface::VERBOSITY_DEBUG,
];
/**
* {@inheritdoc}
*/
public function verbosityMeetsThreshold($verbosityThreshold)
{
if (!isset($this->verbosityMap[$verbosityThreshold])) {
return true;
}
$verbosityThreshold = $this->verbosityMap[$verbosityThreshold];
$verbosity = $this->output()->getVerbosity();
return $verbosity >= $verbosityThreshold;
}
/**
* {@inheritdoc}
*/
public function writeMessage($message)
{
$this->output()->write($message);
}
}
<?php
namespace Robo\Common;
use Robo\Common\ProcessUtils;
/**
* Use this to add arguments and options to the $arguments property.
*/
trait CommandArguments
{
/**
* @var string
*/
protected $arguments = '';
/**
* Pass argument to executable. Its value will be automatically escaped.
*
* @param string $arg
*
* @return $this
*/
public function arg($arg)
{
return $this->args($arg);
}
/**
* Pass methods parameters as arguments to executable. Argument values
* are automatically escaped.
*
* @param string|string[] $args
*
* @return $this
*/
public function args($args)
{
if (!is_array($args)) {
$args = func_get_args();
}
$this->arguments .= ' ' . implode(' ', array_map('static::escape', $args));
return $this;
}
/**
* Pass the provided string in its raw (as provided) form as an argument to executable.
*
* @param string $arg
*
* @return $this
*/
public function rawArg($arg)
{
$this->arguments .= " $arg";
return $this;
}
/**
* Escape the provided value, unless it contains only alphanumeric
* plus a few other basic characters.
*
* @param string $value
*
* @return string
*/
public static function escape($value)
{
if (preg_match('/^[a-zA-Z0-9\/\.@~_-]+$/', $value)) {
return $value;
}
return ProcessUtils::escapeArgument($value);
}
/**
* Pass option to executable. Options are prefixed with `--` , value can be provided in second parameter.
* Option values are automatically escaped.
*
* @param string $option
* @param string $value
* @param string $separator
*
* @return $this
*/
public function option($option, $value = null, $separator = ' ')
{
if ($option !== null and strpos($option, '-') !== 0) {
$option = "--$option";
}
$this->arguments .= null == $option ? '' : " " . $option;
$this->arguments .= null == $value ? '' : $separator . static::escape($value);
return $this;
}
/**
* Pass multiple options to executable. The associative array contains
* the key:value pairs that become `--key value`, for each item in the array.
* Values are automatically escaped.
*
* @param array $options
* @param string $separator
*
* @return $this
*/
public function options(array $options, $separator = ' ')
{
foreach ($options as $option => $value) {
$this->option($option, $value, $separator);
}
return $this;
}
/**
* Pass an option with multiple values to executable. Value can be a string or array.
* Option values are automatically escaped.
*
* @param string $option
* @param string|array $value
* @param string $separator
*
* @return $this
*/
public function optionList($option, $value = array(), $separator = ' ')
{
if (is_array($value)) {
foreach ($value as $item) {
$this->optionList($option, $item, $separator);
}
} else {
$this->option($option, $value, $separator);
}
return $this;
}
}
<?php
namespace Robo\Common;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
trait InputAwareTrait
{
/**
* @var \Symfony\Component\Console\Input\InputInterface
*/
protected $input;
/**
* @param \Symfony\Component\Console\Input\InputInterface $input
*
* @return $this
*
* @see \Symfony\Component\Console\Input\InputAwareInterface::setInput()
*/
public function setInput(InputInterface $input)
{
$this->input = $input;
return $this;
}
/**
* @return \Symfony\Component\Console\Input\InputInterface
*/
protected function input()
{
if (!isset($this->input)) {
$this->setInput(new ArgvInput());
}
return $this->input;
}
/**
* Backwards compatibility.
*
* @return \Symfony\Component\Console\Input\InputInterface
*
* @deprecated
*/
protected function getInput()
{
return $this->input();
}
}
<?php
namespace Robo\Common;
trait ResourceExistenceChecker
{
/**
* Checks if the given input is a file or folder.
*
* @param string|string[] $resources
* @param string $type
* Allowed values: "file", "dir", "fileAndDir"
*
* @return bool
* True if no errors were encountered otherwise false.
*/
protected function checkResources($resources, $type = 'fileAndDir')
{
if (!in_array($type, ['file', 'dir', 'fileAndDir'])) {
throw new \InvalidArgumentException(sprintf('Invalid resource check of type "%s" used!', $type));
}
if (is_string($resources)) {
$resources = [$resources];
}
$success = true;
foreach ($resources as $resource) {
$glob = glob($resource);
if ($glob === false) {
$this->printTaskError(sprintf('Invalid glob "%s"!', $resource), $this);
$success = false;
continue;
}
foreach ($glob as $resource) {
if (!$this->checkResource($resource, $type)) {
$success = false;
}
}
}
return $success;
}
/**
* Checks a single resource, file or directory.
*
* It will print an error as well on the console.
*
* @param string $resource
* File or folder.
* @param string $type
* Allowed values: "file", "dir", "fileAndDir".
*
* @return bool
*/
protected function checkResource($resource, $type)
{
switch ($type) {
case 'file':
if (!$this->isFile($resource)) {
$this->printTaskError(sprintf('File "%s" does not exist!', $resource), $this);
return false;
}
return true;
case 'dir':
if (!$this->isDir($resource)) {
$this->printTaskError(sprintf('Directory "%s" does not exist!', $resource), $this);
return false;
}
return true;
case 'fileAndDir':
if (!$this->isDir($resource) && !$this->isFile($resource)) {
$this->printTaskError(sprintf('File or directory "%s" does not exist!', $resource), $this);
return false;
}
return true;
}
}
/**
* Convenience method to check the often uses "source => target" file / folder arrays.
*
* @param string|array $resources
*/
protected function checkSourceAndTargetResource($resources)
{
if (is_string($resources)) {
$resources = [$resources];
}
$sources = [];
$targets = [];
foreach ($resources as $source => $target) {
$sources[] = $source;
$target[] = $target;
}
$this->checkResources($sources);
$this->checkResources($targets);
}
/**
* Wrapper method around phps is_dir()
*
* @param string $directory
*
* @return bool
*/
protected function isDir($directory)
{
return is_dir($directory);
}
/**
* Wrapper method around phps file_exists()
*
* @param string $file
*
* @return bool
*/
protected function isFile($file)
{
return file_exists($file);
}
}
<?php
namespace Robo\Common;
trait Timer
{
/**
* @var \Robo\Common\TimeKeeper
*/
protected $timer;
protected function startTimer()
{
if (!isset($this->timer)) {
$this->timer = new TimeKeeper();
}
$this->timer->start();
}
protected function stopTimer()
{
if (!isset($this->timer)) {
return;
}
$this->timer->stop();
}
/**
* @return float|null
*/
protected function getExecutionTime()
{
if (!isset($this->timer)) {
return null;
}
return $this->timer->elapsed();
}
}
<?php
namespace Robo\Common;
/**
* This task specifies exactly one shell command.
* It can take additional arguments and options as config parameters.
*/
trait ExecOneCommand
{
use ExecCommand;
use CommandArguments;
}
<?php
namespace Robo\Common;
use Psr\Log\LoggerAwareInterface;
use Robo\Contract\ConfigAwareInterface;
use Robo\Contract\OutputAwareInterface;
use Robo\Contract\VerbosityThresholdInterface;
use Symfony\Component\Process\Process;
class ProcessExecutor implements ConfigAwareInterface, LoggerAwareInterface, OutputAwareInterface, VerbosityThresholdInterface
{
use ExecTrait;
use TaskIO; // uses LoggerAwareTrait and ConfigAwareTrait
use ProgressIndicatorAwareTrait;
use OutputAwareTrait;
/**
* @param \Symfony\Component\Process\Process $process
*/
public function __construct(Process $process)
{
$this->process = $process;
}
/**
* @param \League\Container\ContainerInterface $container
* @param \Symfony\Component\Process\Process $process
*
* @return static
*/
public static function create($container, $process)
{
$processExecutor = new self($process);
$processExecutor->setLogger($container->get('logger'));
$processExecutor->setProgressIndicator($container->get('progressIndicator'));
$processExecutor->setConfig($container->get('config'));
$processExecutor->setOutputAdapter($container->get('outputAdapter'));
return $processExecutor;
}
/**
* {@inheritdoc}
*/
protected function getCommandDescription()
{
return $this->process->getCommandLine();
}
public function run()
{
return $this->execute($this->process);
}
}
<?php
namespace Robo\Common;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
/**
* This task can receive commands from task implementing CommandInterface.
*/
trait CommandReceiver
{
/**
* @param string|\Robo\Contract\CommandInterface $command
*
* @return string
*
* @throws \Robo\Exception\TaskException
*/
protected function receiveCommand($command)
{
if (!is_object($command)) {
return $command;
}
if ($command instanceof CommandInterface) {
return $command->getCommand();
} else {
throw new TaskException($this, get_class($command) . " does not implement CommandInterface, so can't be passed into this task");
}
}
}
<?php
namespace Robo\Common;
use Robo\Robo;
use Robo\TaskInfo;
use Robo\Contract\OutputAdapterInterface;
use Robo\Contract\VerbosityThresholdInterface;
use Consolidation\Log\ConsoleLogLevel;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LogLevel;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Task input/output methods. TaskIO is 'used' in BaseTask, so any
* task that extends this class has access to all of the methods here.
* printTaskInfo, printTaskSuccess, and printTaskError are the three
* primary output methods that tasks are encouraged to use. Tasks should
* avoid using the IO trait output methods.
*/
trait VerbosityThresholdTrait
{
/**
* @var \Robo\Contract\OutputAdapterInterface
*/
protected $outputAdapter;
/**
* @var int
*/
protected $verbosityThreshold = 0;
/**
* Required verbosity level before any TaskIO output will be produced.
* e.g. OutputInterface::VERBOSITY_VERBOSE
*
* @param int $verbosityThreshold
*
* @return $this
*/
public function setVerbosityThreshold($verbosityThreshold)
{
$this->verbosityThreshold = $verbosityThreshold;
return $this;
}
/**
* @return int
*/
public function verbosityThreshold()
{
return $this->verbosityThreshold;
}
public function setOutputAdapter(OutputAdapterInterface $outputAdapter)
{
$this->outputAdapter = $outputAdapter;
}
/**
* @return \Robo\Contract\OutputAdapterInterface
*/
public function outputAdapter()
{
return $this->outputAdapter;
}
/**
* @return bool
*/
public function hasOutputAdapter()
{
return isset($this->outputAdapter);
}
/**
* @return bool
*/
public function verbosityMeetsThreshold()
{
if ($this->hasOutputAdapter()) {
return $this->outputAdapter()->verbosityMeetsThreshold($this->verbosityThreshold());
}
return true;
}
/**
* Print a message if the selected verbosity level is over this task's
* verbosity threshold.
*
* @param string $message
*/
public function writeMessage($message)
{
if (!$this->verbosityMeetsThreshold()) {
return;
}
$this->outputAdapter()->writeMessage($message);
}
}
<?php
namespace Robo\Common;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Contract\VerbosityThresholdInterface;
trait ProgressIndicatorAwareTrait
{
use Timer;
/**
* @var null|\Robo\Common\ProgressIndicator
*/
protected $progressIndicator;
/**
* @return int
*/
public function progressIndicatorSteps()
{
return 0;
}
/**
* @param null|\Robo\Common\ProgressIndicator $progressIndicator
*
* @return $this
*/
public function setProgressIndicator($progressIndicator)
{
$this->progressIndicator = $progressIndicator;
return $this;
}
/**
* @return null|bool
*/
protected function hideProgressIndicator()
{
if (!$this->progressIndicator) {
return;
}
return $this->progressIndicator->hideProgressIndicator();
}
protected function showProgressIndicator()
{
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->showProgressIndicator();
}
/**
* @param bool $visible
*/
protected function restoreProgressIndicator($visible)
{
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->restoreProgressIndicator($visible);
}
/**
* @return int
*/
protected function getTotalExecutionTime()
{
if (!$this->progressIndicator) {
return 0;
}
return $this->progressIndicator->getExecutionTime();
}
protected function startProgressIndicator()
{
$this->startTimer();
if ($this instanceof VerbosityThresholdInterface
&& !$this->verbosityMeetsThreshold()
) {
return;
}
if (!$this->progressIndicator) {
return;
}
$totalSteps = $this->progressIndicatorSteps();
$this->progressIndicator->startProgressIndicator($totalSteps, $this);
}
/**
* @return bool
*/
protected function inProgress()
{
if (!$this->progressIndicator) {
return false;
}
return $this->progressIndicator->inProgress();
}
protected function stopProgressIndicator()
{
$this->stopTimer();
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->stopProgressIndicator($this);
}
protected function disableProgressIndicator()
{
$this->stopTimer();
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->disableProgressIndicator();
}
protected function detatchProgressIndicator()
{
$this->setProgressIndicator(null);
}
/**
* @param int $steps
*/
protected function advanceProgressIndicator($steps = 1)
{
if (!$this->progressIndicator) {
return;
}
$this->progressIndicator->advanceProgressIndicator($steps);
}
}
<?php
namespace Robo\Common;
/**
* Wrapper around \Symfony\Component\Console\Helper\ProgressBar
*/
class ProgressIndicator
{
use Timer;
/**
* @var \Symfony\Component\Console\Helper\ProgressBar
*/
protected $progressBar;
/**
* @var \Symfony\Component\Console\Output\OutputInterface
*/
protected $output;
/**
* @var bool
*/
protected $progressIndicatorRunning = false;
/**
* @var int
*/
protected $autoDisplayInterval = 0;
/**
* @var int
*/
protected $cachedSteps = 0;
/**
* @var int
*/
protected $totalSteps = 0;
/**
* @var bool
*/
protected $progressBarDisplayed = false;
/**
* @var \Robo\Contract\TaskInterface
*/
protected $owner;
/**
* @param \Symfony\Component\Console\Helper\ProgressBar $progressBar
* @param \Symfony\Component\Console\Output\OutputInterface $output
*/
public function __construct($progressBar, \Symfony\Component\Console\Output\OutputInterface $output)
{
$this->progressBar = $progressBar;
$this->output = $output;
}
/**
* @param int $interval
*/
public function setProgressBarAutoDisplayInterval($interval)
{
if ($this->progressIndicatorRunning) {
return;
}
$this->autoDisplayInterval = $interval;
}
/**
* @return bool
*/
public function hideProgressIndicator()
{
$result = $this->progressBarDisplayed;
if ($this->progressIndicatorRunning && $this->progressBarDisplayed) {
$this->progressBar->clear();
// Hack: progress indicator does not reset cursor to beginning of line on 'clear'
$this->output->write("\x0D");
$this->progressBarDisplayed = false;
}
return $result;
}
public function showProgressIndicator()
{
if ($this->progressIndicatorRunning && !$this->progressBarDisplayed && isset($this->progressBar)) {
$this->progressBar->display();
$this->progressBarDisplayed = true;
$this->advanceProgressIndicatorCachedSteps();
}
}
/**
* @param bool $visible
*/
public function restoreProgressIndicator($visible)
{
if ($visible) {
$this->showProgressIndicator();
}
}
/**
* @param int $totalSteps
* @param \Robo\Contract\TaskInterface $owner
*/
public function startProgressIndicator($totalSteps, $owner)
{
if (!isset($this->progressBar)) {
return;
}
$this->progressIndicatorRunning = true;
if (!isset($this->owner)) {
$this->owner = $owner;
$this->startTimer();
$this->totalSteps = $totalSteps;
$this->autoShowProgressIndicator();
}
}
public function autoShowProgressIndicator()
{
if (($this->autoDisplayInterval < 0) || !isset($this->progressBar) || !$this->output->isDecorated()) {
return;
}
if ($this->autoDisplayInterval <= $this->getExecutionTime()) {
$this->autoDisplayInterval = -1;
$this->progressBar->start($this->totalSteps);
$this->showProgressIndicator();
}
}
/**
* @return bool
*/
public function inProgress()
{
return $this->progressIndicatorRunning;
}
/**
* @param \Robo\Contract\TaskInterface $owner
*/
public function stopProgressIndicator($owner)
{
if ($this->progressIndicatorRunning && ($this->owner === $owner)) {
$this->cleanup();
}
}
protected function cleanup()
{
$this->progressIndicatorRunning = false;
$this->owner = null;
if ($this->progressBarDisplayed) {
$this->progressBar->finish();
// Hack: progress indicator does not always finish cleanly
$this->output->writeln('');
$this->progressBarDisplayed = false;
}
$this->stopTimer();
}
/**
* Erase progress indicator and ensure it never returns. Used
* only during error handlers or to permanently remove the progress bar.
*/
public function disableProgressIndicator()
{
$this->cleanup();
// ProgressIndicator is shared, so this permanently removes
// the program's ability to display progress bars.
$this->progressBar = null;
}
/**
* @param int $steps
*/
public function advanceProgressIndicator($steps = 1)
{
$this->cachedSteps += $steps;
if ($this->progressIndicatorRunning) {
$this->autoShowProgressIndicator();
// We only want to call `advance` if the progress bar is visible,
// because it always displays itself when it is advanced.
if ($this->progressBarDisplayed) {
return $this->advanceProgressIndicatorCachedSteps();
}
}
}
protected function advanceProgressIndicatorCachedSteps()
{
$this->progressBar->advance($this->cachedSteps);
$this->cachedSteps = 0;
}
}
<?php
namespace Robo\Common;
use Robo\Robo;
use Robo\TaskInfo;
use Consolidation\Log\ConsoleLogLevel;
use Psr\Log\LoggerAwareTrait;
use Psr\Log\LogLevel;
use Robo\Contract\ProgressIndicatorAwareInterface;
/**
* Task input/output methods. TaskIO is 'used' in BaseTask, so any
* task that extends this class has access to all of the methods here.
* printTaskInfo, printTaskSuccess, and printTaskError are the three
* primary output methods that tasks are encouraged to use. Tasks should
* avoid using the IO trait output methods.
*/
trait TaskIO
{
use LoggerAwareTrait;
use ConfigAwareTrait;
use VerbosityThresholdTrait;
/**
* @return null|\Psr\Log\LoggerInterface
*/
public function logger()
{
// $this->logger should always be set in Robo core tasks.
if ($this->logger) {
return $this->logger;
}
// TODO: Remove call to Robo::logger() once maintaining backwards
// compatibility with legacy external Robo tasks is no longer desired.
if (!Robo::logger()) {
return null;
}
static $gaveDeprecationWarning = false;
if (!$gaveDeprecationWarning) {
trigger_error('No logger set for ' . get_class($this) . '. Use $this->task(Foo::class) rather than new Foo() in loadTasks to ensure the builder can initialize task the task, or use $this->collectionBuilder()->taskFoo() if creating one task from within another.', E_USER_DEPRECATED);
$gaveDeprecationWarning = true;
}
return Robo::logger();
}
/**
* Print information about a task in progress.
*
* With the Symfony Console logger, NOTICE is displayed at VERBOSITY_VERBOSE
* and INFO is displayed at VERBOSITY_VERY_VERBOSE.
*
* Robo overrides the default such that NOTICE is displayed at
* VERBOSITY_NORMAL and INFO is displayed at VERBOSITY_VERBOSE.
*
* n.b. We should probably have printTaskNotice for our ordinary
* output, and use printTaskInfo for less interesting messages.
*
* @param string $text
* @param null|array $context
*/
protected function printTaskInfo($text, $context = null)
{
// The 'note' style is used for both 'notice' and 'info' log levels;
// However, 'notice' is printed at VERBOSITY_NORMAL, whereas 'info'
// is only printed at VERBOSITY_VERBOSE.
$this->printTaskOutput(LogLevel::NOTICE, $text, $this->getTaskContext($context));
}
/**
* Provide notification that some part of the task succeeded.
*
* With the Symfony Console logger, success messages are remapped to NOTICE,
* and displayed in VERBOSITY_VERBOSE. When used with the Robo logger,
* success messages are displayed at VERBOSITY_NORMAL.
*
* @param string $text
* @param null|array $context
*/
protected function printTaskSuccess($text, $context = null)
{
// Not all loggers will recognize ConsoleLogLevel::SUCCESS.
// We therefore log as LogLevel::NOTICE, and apply a '_level'
// override in the context so that this message will be
// logged as SUCCESS if that log level is recognized.
$context['_level'] = ConsoleLogLevel::SUCCESS;
$this->printTaskOutput(LogLevel::NOTICE, $text, $this->getTaskContext($context));
}
/**
* Provide notification that there is something wrong, but
* execution can continue.
*
* Warning messages are displayed at VERBOSITY_NORMAL.
*
* @param string $text
* @param null|array $context
*/
protected function printTaskWarning($text, $context = null)
{
$this->printTaskOutput(LogLevel::WARNING, $text, $this->getTaskContext($context));
}
/**
* Provide notification that some operation in the task failed,
* and the task cannot continue.
*
* Error messages are displayed at VERBOSITY_NORMAL.
*
* @param string $text
* @param null|array $context
*/
protected function printTaskError($text, $context = null)
{
$this->printTaskOutput(LogLevel::ERROR, $text, $this->getTaskContext($context));
}
/**
* Provide debugging notification. These messages are only
* displayed if the log level is VERBOSITY_DEBUG.
*
* @param string$text
* @param null|array $context
*/
protected function printTaskDebug($text, $context = null)
{
$this->printTaskOutput(LogLevel::DEBUG, $text, $this->getTaskContext($context));
}
/**
* @param string $level
* One of the \Psr\Log\LogLevel constant
* @param string $text
* @param null|array $context
*/
protected function printTaskOutput($level, $text, $context)
{
if (!$this->verbosityMeetsThreshold()) {
return;
}
$logger = $this->logger();
if (!$logger) {
return;
}
// Hide the progress indicator, if it is visible.
$inProgress = $this->hideTaskProgress();
$logger->log($level, $text, $this->getTaskContext($context));
// After we have printed our log message, redraw the progress indicator.
$this->showTaskProgress($inProgress);
}
/**
* @return bool
*/
protected function hideTaskProgress()
{
$inProgress = false;
if ($this instanceof ProgressIndicatorAwareInterface) {
$inProgress = $this->inProgress();
}
// If a progress indicator is running on this task, then we mush
// hide it before we print anything, or its display will be overwritten.
if ($inProgress) {
$inProgress = $this->hideProgressIndicator();
}
return $inProgress;
}
/**
* @param bool $inProgress
*/
protected function showTaskProgress($inProgress)
{
if ($inProgress) {
$this->restoreProgressIndicator($inProgress);
}
}
/**
* Format a quantity of bytes.
*
* @param int $size
* @param int $precision
*
* @return string
*/
protected function formatBytes($size, $precision = 2)
{
if ($size === 0) {
return 0;
}
$base = log($size, 1024);
$suffixes = array('', 'k', 'M', 'G', 'T');
return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
}
/**
* Get the formatted task name for use in task output.
* This is placed in the task context under 'name', and
* used as the log label by Robo\Common\RoboLogStyle,
* which is inserted at the head of log messages by
* Robo\Common\CustomLogStyle::formatMessage().
*
* @param null|object $task
*
* @return string
*/
protected function getPrintedTaskName($task = null)
{
if (!$task) {
$task = $this;
}
return TaskInfo::formatTaskName($task);
}
/**
* @param null|array $context
*
* @return array
* Context information.
*/
protected function getTaskContext($context = null)
{
if (!$context) {
$context = [];
}
if (!is_array($context)) {
$context = ['task' => $context];
}
if (!array_key_exists('task', $context)) {
$context['task'] = $this;
}
return $context + TaskInfo::getTaskContext($context['task']);
}
}
<?php
/*
* This file is derived from part of the Symfony package, which is
* (c) Fabien Potencier <fabien@symfony.com>
*/
namespace Robo\Common;
use Symfony\Component\Process\Exception\InvalidArgumentException;
/**
* ProcessUtils is a bunch of utility methods. We want to allow Robo 1.x
* to work with Symfony 4.x while remaining backwards compatibility. This
* requires us to replace some deprecated functionality removed in Symfony.
*/
class ProcessUtils
{
/**
* This class should not be instantiated.
*/
private function __construct()
{
}
/**
* Escapes a string to be used as a shell argument.
*
* @param string $argument
* The argument that will be escaped.
*
* @return string
* The escaped argument.
*
* @deprecated since version 3.3, to be removed in 4.0. Use a command line array or give env vars to the `Process::start/run()` method instead.
*/
public static function escapeArgument($argument)
{
@trigger_error('The ' . __METHOD__ . '() method is a copy of a method that was deprecated by Symfony 3.3 and removed in Symfony 4; it will be removed in Robo 2.0.', E_USER_DEPRECATED);
//Fix for PHP bug #43784 escapeshellarg removes % from given string
//Fix for PHP bug #49446 escapeshellarg doesn't work on Windows
//@see https://bugs.php.net/bug.php?id=43784
//@see https://bugs.php.net/bug.php?id=49446
if ('\\' === DIRECTORY_SEPARATOR) {
if ('' === $argument) {
return escapeshellarg($argument);
}
$escapedArgument = '';
$quote = false;
foreach (preg_split('/(")/', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) {
if ('"' === $part) {
$escapedArgument .= '\\"';
} elseif (self::isSurroundedBy($part, '%')) {
// Avoid environment variable expansion
$escapedArgument .= '^%"' . substr($part, 1, -1) . '"^%';
} else {
// escape trailing backslash
if ('\\' === substr($part, -1)) {
$part .= '\\';
}
$quote = true;
$escapedArgument .= $part;
}
}
if ($quote) {
$escapedArgument = '"' . $escapedArgument . '"';
}
return $escapedArgument;
}
return "'" . str_replace("'", "'\\''", $argument) . "'";
}
private static function isSurroundedBy($arg, $char)
{
return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1];
}
}
<?php
namespace Robo\Common;
use Robo\Result;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
/**
* This task is supposed to be executed as shell command.
* You can specify working directory and if output is printed.
*/
trait ExecCommand
{
use ExecTrait;
/**
* @var \Robo\Common\TimeKeeper
*/
protected $execTimer;
/**
* @return \Robo\Common\TimeKeeper
*/
protected function getExecTimer()
{
if (!isset($this->execTimer)) {
$this->execTimer = new TimeKeeper();
}
return $this->execTimer;
}
/**
* Look for a "{$cmd}.phar" in the current working
* directory; return a string to exec it if it is
* found. Otherwise, look for an executable command
* of the same name via findExecutable.
*
* @param string $cmd
*
* @return bool|string
*/
protected function findExecutablePhar($cmd)
{
if (file_exists("{$cmd}.phar")) {
return "php {$cmd}.phar";
}
return $this->findExecutable($cmd);
}
/**
* Return the best path to the executable program
* with the provided name. Favor vendor/bin in the
* current project. If not found there, use
* whatever is on the $PATH.
*
* @param string $cmd
*
* @return bool|string
*/
protected function findExecutable($cmd)
{
$pathToCmd = $this->searchForExecutable($cmd);
if ($pathToCmd) {
return $this->useCallOnWindows($pathToCmd);
}
return false;
}
/**
* @param string $cmd
*
* @return string
*/
private function searchForExecutable($cmd)
{
$projectBin = $this->findProjectBin();
$localComposerInstallation = $projectBin . DIRECTORY_SEPARATOR . $cmd;
if (file_exists($localComposerInstallation)) {
return $localComposerInstallation;
}
$finder = new ExecutableFinder();
return $finder->find($cmd, null, []);
}
/**
* @return bool|string
*/
protected function findProjectBin()
{
$cwd = getcwd();
$candidates = [ __DIR__ . '/../../vendor/bin', __DIR__ . '/../../bin', $cwd . '/vendor/bin' ];
// If this project is inside a vendor directory, give highest priority
// to that directory.
$vendorDirContainingUs = realpath(__DIR__ . '/../../../..');
if (is_dir($vendorDirContainingUs) && (basename($vendorDirContainingUs) == 'vendor')) {
array_unshift($candidates, $vendorDirContainingUs . '/bin');
}
foreach ($candidates as $dir) {
if (is_dir("$dir")) {
return realpath($dir);
}
}
return false;
}
/**
* Wrap Windows executables in 'call' per 7a88757d
*
* @param string $cmd
*
* @return string
*/
protected function useCallOnWindows($cmd)
{
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
if (file_exists("{$cmd}.bat")) {
$cmd = "{$cmd}.bat";
}
return "call $cmd";
}
return $cmd;
}
/**
* {@inheritdoc}
*/
protected function getCommandDescription()
{
return $this->process->getCommandLine();
}
/**
* @param string $command
*
* @return \Robo\Result
*/
protected function executeCommand($command)
{
// TODO: Symfony 4 requires that we supply the working directory.
$result_data = $this->execute(new Process($command, getcwd()));
return new Result(
$this,
$result_data->getExitCode(),
$result_data->getMessage(),
$result_data->getData()
);
}
}
<?php
namespace Robo\Task\Docker;
/**
* Stops Docker container
*
* ```php
* <?php
* $this->taskDockerStop($cidOrResult)
* ->run();
* ?>
* ```
*/
class Stop extends Base
{
/**
* @var string
*/
protected $command = "docker stop";
/**
* @var null|string
*/
protected $cid;
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*/
public function __construct($cidOrResult)
{
$this->cid = $cidOrResult instanceof Result ? $cidOrResult->getCid() : $cidOrResult;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->arguments . ' ' . $this->cid;
}
}
<?php
namespace Robo\Task\Docker;
/**
* Builds Docker image
*
* ```php
* <?php
* $this->taskDockerBuild()->run();
*
* $this->taskDockerBuild('path/to/dir')
* ->tag('database')
* ->run();
*
* ?>
*
* ```
*
* Class Build
* @package Robo\Task\Docker
*/
class Build extends Base
{
/**
* @var string
*/
protected $path;
/**
* @param string $path
*/
public function __construct($path = '.')
{
$this->command = "docker build";
$this->path = $path;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->arguments . ' ' . $this->path;
}
/**
* @param string $tag
*
* @return $this
*/
public function tag($tag)
{
return $this->option('-t', $tag);
}
}
<?php
namespace Robo\Task\Docker;
class Result extends \Robo\Result
{
/**
* Do not print result, as it was already printed
*/
protected function printResult()
{
}
/**
* @return null|string
*/
public function getCid()
{
if (isset($this['cid'])) {
return $this['cid'];
}
return null;
}
/**
* @return null|string
*/
public function getContainerName()
{
if (isset($this['name'])) {
return $this['name'];
}
return null;
}
}
<?php
namespace Robo\Task\Docker;
/**
* Commits docker container to an image
*
* ```
* $this->taskDockerCommit($containerId)
* ->name('my/database')
* ->run();
*
* // alternatively you can take the result from DockerRun task:
*
* $result = $this->taskDockerRun('db')
* ->exec('./prepare_database.sh')
* ->run();
*
* $task->dockerCommit($result)
* ->name('my/database')
* ->run();
* ```
*/
class Commit extends Base
{
/**
* @var string
*/
protected $command = "docker commit";
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $cid;
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*/
public function __construct($cidOrResult)
{
$this->cid = $cidOrResult instanceof Result ? $cidOrResult->getCid() : $cidOrResult;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->cid . ' ' . $this->name . ' ' . $this->arguments;
}
/**
* @param string $name
*
* @return $this
*/
public function name($name)
{
$this->name = $name;
return $this;
}
}
<?php
namespace Robo\Task\Docker;
use Robo\Common\CommandReceiver;
/**
* Performs `docker run` on a container.
*
* ```php
* <?php
* $this->taskDockerRun('mysql')->run();
*
* $result = $this->taskDockerRun('my_db_image')
* ->env('DB', 'database_name')
* ->volume('/path/to/data', '/data')
* ->detached()
* ->publish(3306)
* ->name('my_mysql')
* ->run();
*
* // retrieve container's cid:
* $this->say("Running container ".$result->getCid());
*
* // execute script inside container
* $result = $this->taskDockerRun('db')
* ->exec('prepare_test_data.sh')
* ->run();
*
* $this->taskDockerCommit($result)
* ->name('test_db')
* ->run();
*
* // link containers
* $mysql = $this->taskDockerRun('mysql')
* ->name('wp_db') // important to set name for linked container
* ->env('MYSQL_ROOT_PASSWORD', '123456')
* ->run();
*
* $this->taskDockerRun('wordpress')
* ->link($mysql)
* ->publish(80, 8080)
* ->detached()
* ->run();
*
* ?>
* ```
*
*/
class Run extends Base
{
use CommandReceiver;
/**
* @var string
*/
protected $image = '';
/**
* @var string
*/
protected $run = '';
/**
* @var string
*/
protected $cidFile;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $dir;
/**
* @param string $image
*/
public function __construct($image)
{
$this->image = $image;
}
/**
* {@inheritdoc}
*/
public function getPrinted()
{
return $this->isPrinted;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
if ($this->isPrinted) {
$this->option('-i');
}
if ($this->cidFile) {
$this->option('cidfile', $this->cidFile);
}
return trim('docker run ' . $this->arguments . ' ' . $this->image . ' ' . $this->run);
}
/**
* @return $this
*/
public function detached()
{
$this->option('-d');
return $this;
}
/**
* {@inheritdoc}
*/
public function interactive($interactive = true)
{
if ($interactive) {
$this->option('-i');
}
return parent::interactive($interactive);
}
/**
* @param string|\Robo\Contract\CommandInterface $run
*
* @return $this
*/
public function exec($run)
{
$this->run = $this->receiveCommand($run);
return $this;
}
/**
* @param string $from
* @param null|string $to
*
* @return $this
*/
public function volume($from, $to = null)
{
$volume = $to ? "$from:$to" : $from;
$this->option('-v', $volume);
return $this;
}
/**
* Set environment variables.
* n.b. $this->env($variable, $value) also available here,
* inherited from ExecTrait.
*
* @param array $env
*
* @return $this
*/
public function envVars(array $env)
{
foreach ($env as $variable => $value) {
$this->setDockerEnv($variable, $value);
}
return $this;
}
/**
* @param string $variable
* @param null|string $value
*
* @return $this
*/
protected function setDockerEnv($variable, $value = null)
{
$env = $value ? "$variable=$value" : $variable;
return $this->option("-e", $env);
}
/**
* @param null|int $port
* @param null|int $portTo
*
* @return $this
*/
public function publish($port = null, $portTo = null)
{
if (!$port) {
return $this->option('-P');
}
if ($portTo) {
$port = "$port:$portTo";
}
return $this->option('-p', $port);
}
/**
* @param string $dir
*
* @return $this
*/
public function containerWorkdir($dir)
{
return $this->option('-w', $dir);
}
/**
* @param string $user
*
* @return $this
*/
public function user($user)
{
return $this->option('-u', $user);
}
/**
* @return $this
*/
public function privileged()
{
return $this->option('--privileged');
}
/**
* @param string $name
*
* @return $this
*/
public function name($name)
{
$this->name = $name;
return $this->option('name', $name);
}
/**
* @param string|\Robo\Task\Docker\Result $name
* @param string $alias
*
* @return $this
*/
public function link($name, $alias)
{
if ($name instanceof Result) {
$name = $name->getContainerName();
}
$this->option('link', "$name:$alias");
return $this;
}
/**
* @param string $dir
*
* @return $this
*/
public function tmpDir($dir)
{
$this->dir = $dir;
return $this;
}
/**
* @return string
*/
public function getTmpDir()
{
return $this->dir ? $this->dir : sys_get_temp_dir();
}
/**
* @return string
*/
public function getUniqId()
{
return uniqid();
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->cidFile = $this->getTmpDir() . '/docker_' . $this->getUniqId() . '.cid';
$result = parent::run();
$result['cid'] = $this->getCid();
return $result;
}
/**
* @return null|string
*/
protected function getCid()
{
if (!$this->cidFile || !file_exists($this->cidFile)) {
return null;
}
$cid = trim(file_get_contents($this->cidFile));
@unlink($this->cidFile);
return $cid;
}
}
<?php
namespace Robo\Task\Docker;
use Robo\Common\ExecOneCommand;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Task\BaseTask;
abstract class Base extends BaseTask implements CommandInterface, PrintedInterface
{
use ExecOneCommand;
/**
* @var string
*/
protected $command = '';
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
return $this->executeCommand($command);
}
abstract public function getCommand();
}
<?php
namespace Robo\Task\Docker;
/**
* Remove docker container
*
* ```php
* <?php
* $this->taskDockerRemove($container)
* ->run();
* ?>
* ```
*
*/
class Remove extends Base
{
/**
* @param string $container
*/
public function __construct($container)
{
$this->command = "docker rm $container ";
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->arguments;
}
}
<?php
namespace Robo\Task\Docker;
use Robo\Common\CommandReceiver;
/**
* Executes command inside running Docker container
*
* ```php
* <?php
* $test = $this->taskDockerRun('test_env')
* ->detached()
* ->run();
*
* $this->taskDockerExec($test)
* ->interactive()
* ->exec('./runtests')
* ->run();
*
* // alternatively use commands from other tasks
*
* $this->taskDockerExec($test)
* ->interactive()
* ->exec($this->taskCodecept()->suite('acceptance'))
* ->run();
* ?>
* ```
*
*/
class Exec extends Base
{
use CommandReceiver;
/**
* @var string
*/
protected $command = "docker exec";
/**
* @var string
*/
protected $cid;
/**
* @var string
*/
protected $run = '';
/**
* @param string|\Robo\Result $cidOrResult
*/
public function __construct($cidOrResult)
{
$this->cid = $cidOrResult instanceof Result ? $cidOrResult->getCid() : $cidOrResult;
}
/**
* @return $this
*/
public function detached()
{
$this->option('-d');
return $this;
}
/**
* {@inheritdoc}
*/
public function interactive($interactive = true)
{
if ($interactive) {
$this->option('-i');
}
return parent::interactive($interactive);
}
/**
* @param string|\Robo\Contract\CommandInterface $command
*
* @return $this
*/
public function exec($command)
{
$this->run = $this->receiveCommand($command);
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->arguments . ' ' . $this->cid . ' ' . $this->run;
}
}
<?php
namespace Robo\Task\Docker;
trait loadTasks
{
/**
* @param string $image
*
* @return \Robo\Task\Docker\Run|\Robo\Collection\CollectionBuilder
*/
protected function taskDockerRun($image)
{
return $this->task(Run::class, $image);
}
/**
* @param string $image
*
* @return \Robo\Task\Docker\Pull|\Robo\Collection\CollectionBuilder
*/
protected function taskDockerPull($image)
{
return $this->task(Pull::class, $image);
}
/**
* @param string $path
*
* @return \Robo\Task\Docker\Build|\Robo\Collection\CollectionBuilder
*/
protected function taskDockerBuild($path = '.')
{
return $this->task(Build::class, $path);
}
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*
* @return \Robo\Task\Docker\Stop|\Robo\Collection\CollectionBuilder
*/
protected function taskDockerStop($cidOrResult)
{
return $this->task(Stop::class, $cidOrResult);
}
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*
* @return \Robo\Task\Docker\Commit|\Robo\Collection\CollectionBuilder
*/
protected function taskDockerCommit($cidOrResult)
{
return $this->task(Commit::class, $cidOrResult);
}
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*
* @return \Robo\Task\Docker\Start|\Robo\Collection\CollectionBuilder
*/
protected function taskDockerStart($cidOrResult)
{
return $this->task(Start::class, $cidOrResult);
}
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*
* @return \Robo\Task\Docker\Remove|\Robo\Collection\CollectionBuilder
*/
protected function taskDockerRemove($cidOrResult)
{
return $this->task(Remove::class, $cidOrResult);
}
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*
* @return \Robo\Task\Docker\Exec|\Robo\Collection\CollectionBuilder
*/
protected function taskDockerExec($cidOrResult)
{
return $this->task(Exec::class, $cidOrResult);
}
}
<?php
namespace Robo\Task\Docker;
/**
* Pulls an image from DockerHub
*
* ```php
* <?php
* $this->taskDockerPull('wordpress')
* ->run();
*
* ?>
* ```
*
*/
class Pull extends Base
{
/**
* @param string $image
*/
public function __construct($image)
{
$this->command = "docker pull $image ";
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->arguments;
}
}
<?php
namespace Robo\Task\Docker;
/**
* Starts Docker container
*
* ```php
* <?php
* $this->taskDockerStart($cidOrResult)
* ->run();
* ?>
* ```
*/
class Start extends Base
{
/**
* @var string
*/
protected $command = "docker start";
/**
* @var null|string
*/
protected $cid;
/**
* @param string|\Robo\Task\Docker\Result $cidOrResult
*/
public function __construct($cidOrResult)
{
$this->cid = $cidOrResult instanceof Result ? $cidOrResult->getCid() : $cidOrResult;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . ' ' . $this->arguments . ' ' . $this->cid;
}
}
<?php
namespace Robo\Task\Archive;
use Robo\Result;
use Robo\Task\BaseTask;
use Robo\Task\Filesystem\FilesystemStack;
use Robo\Task\Filesystem\DeleteDir;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Extracts an archive.
*
* Note that often, distributions are packaged in tar or zip archives
* where the topmost folder may contain variable information, such as
* the release date, or the version of the package. This information
* is very useful when unpacking by hand, but arbitrarily-named directories
* are much less useful to scripts. Therefore, by default, Extract will
* remove the top-level directory, and instead store all extracted files
* into the directory specified by $archivePath.
*
* To keep the top-level directory when extracting, use
* `preserveTopDirectory(true)`.
*
* ``` php
* <?php
* $this->taskExtract($archivePath)
* ->to($destination)
* ->preserveTopDirectory(false) // the default
* ->run();
* ?>
* ```
*/
class Extract extends BaseTask implements BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var string
*/
protected $filename;
/**
* @var string
*/
protected $to;
/**
* @var bool
*/
private $preserveTopDirectory = false;
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* Location to store extracted files.
*
* @param string $to
*
* @return $this
*/
public function to($to)
{
$this->to = $to;
return $this;
}
/**
* @param bool $preserve
*
* @return $this
*/
public function preserveTopDirectory($preserve = true)
{
$this->preserveTopDirectory = $preserve;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (!file_exists($this->filename)) {
$this->printTaskError("File {filename} does not exist", ['filename' => $this->filename]);
return false;
}
if (!($mimetype = static::archiveType($this->filename))) {
$this->printTaskError("Could not determine type of archive for {filename}", ['filename' => $this->filename]);
return false;
}
// We will first extract to $extractLocation and then move to $this->to
$extractLocation = static::getTmpDir();
@mkdir($extractLocation);
@mkdir(dirname($this->to));
$this->startTimer();
$this->printTaskInfo("Extracting {filename}", ['filename' => $this->filename]);
$result = $this->extractAppropriateType($mimetype, $extractLocation);
if ($result->wasSuccessful()) {
$this->printTaskInfo("{filename} extracted", ['filename' => $this->filename]);
// Now, we want to move the extracted files to $this->to. There
// are two possibilities that we must consider:
//
// (1) Archived files were encapsulated in a folder with an arbitrary name
// (2) There was no encapsulating folder, and all the files in the archive
// were extracted into $extractLocation
//
// In the case of (1), we want to move and rename the encapsulating folder
// to $this->to.
//
// In the case of (2), we will just move and rename $extractLocation.
$filesInExtractLocation = glob("$extractLocation/*");
$hasEncapsulatingFolder = ((count($filesInExtractLocation) == 1) && is_dir($filesInExtractLocation[0]));
if ($hasEncapsulatingFolder && !$this->preserveTopDirectory) {
$result = (new FilesystemStack())
->inflect($this)
->rename($filesInExtractLocation[0], $this->to)
->run();
(new DeleteDir($extractLocation))
->inflect($this)
->run();
} else {
$result = (new FilesystemStack())
->inflect($this)
->rename($extractLocation, $this->to)
->run();
}
}
$this->stopTimer();
$result['time'] = $this->getExecutionTime();
return $result;
}
/**
* @param string $mimetype
* @param string $extractLocation
*
* @return \Robo\Result
*/
protected function extractAppropriateType($mimetype, $extractLocation)
{
// Perform the extraction of a zip file.
if (($mimetype == 'application/zip') || ($mimetype == 'application/x-zip')) {
return $this->extractZip($extractLocation);
}
return $this->extractTar($extractLocation);
}
/**
* @param string $extractLocation
*
* @return \Robo\Result
*/
protected function extractZip($extractLocation)
{
if (!extension_loaded('zlib')) {
return Result::errorMissingExtension($this, 'zlib', 'zip extracting');
}
$zip = new \ZipArchive();
if (($status = $zip->open($this->filename)) !== true) {
return Result::error($this, "Could not open zip archive {$this->filename}");
}
if (!$zip->extractTo($extractLocation)) {
return Result::error($this, "Could not extract zip archive {$this->filename}");
}
$zip->close();
return Result::success($this);
}
/**
* @param string $extractLocation
*
* @return \Robo\Result
*/
protected function extractTar($extractLocation)
{
if (!class_exists('Archive_Tar')) {
return Result::errorMissingPackage($this, 'Archive_Tar', 'pear/archive_tar');
}
$tar_object = new \Archive_Tar($this->filename);
if (!$tar_object->extract($extractLocation)) {
return Result::error($this, "Could not extract tar archive {$this->filename}");
}
return Result::success($this);
}
/**
* @param string $filename
*
* @return bool|string
*/
protected static function archiveType($filename)
{
$content_type = false;
if (class_exists('finfo')) {
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$content_type = $finfo->file($filename);
// If finfo cannot determine the content type, then we will try other methods
if ($content_type == 'application/octet-stream') {
$content_type = false;
}
}
// Examing the file's magic header bytes.
if (!$content_type) {
if ($file = fopen($filename, 'rb')) {
$first = fread($file, 2);
fclose($file);
if ($first !== false) {
// Interpret the two bytes as a little endian 16-bit unsigned int.
$data = unpack('v', $first);
switch ($data[1]) {
case 0x8b1f:
// First two bytes of gzip files are 0x1f, 0x8b (little-endian).
// See http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
$content_type = 'application/x-gzip';
break;
case 0x4b50:
// First two bytes of zip files are 0x50, 0x4b ('PK') (little-endian).
// See http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
$content_type = 'application/zip';
break;
case 0x5a42:
// First two bytes of bzip2 files are 0x5a, 0x42 ('BZ') (big-endian).
// See http://en.wikipedia.org/wiki/Bzip2#File_format
$content_type = 'application/x-bzip2';
break;
}
}
}
}
// 3. Lastly if above methods didn't work, try to guess the mime type from
// the file extension. This is useful if the file has no identificable magic
// header bytes (for example tarballs).
if (!$content_type) {
// Remove querystring from the filename, if present.
$filename = basename(current(explode('?', $filename, 2)));
$extension_mimetype = array(
'.tar.gz' => 'application/x-gzip',
'.tgz' => 'application/x-gzip',
'.tar' => 'application/x-tar',
);
foreach ($extension_mimetype as $extension => $ct) {
if (substr($filename, -strlen($extension)) === $extension) {
$content_type = $ct;
break;
}
}
}
return $content_type;
}
/**
* @return string
*/
protected static function getTmpDir()
{
return getcwd() . '/tmp' . rand() . time();
}
}
<?php
namespace Robo\Task\Archive;
trait loadTasks
{
/**
* @param string $filename
*
* @return \Robo\Task\Archive\Pack|\Robo\Collection\CollectionBuilder
*/
protected function taskPack($filename)
{
return $this->task(Pack::class, $filename);
}
/**
* @param string $filename
*
* @return \Robo\Task\Archive\Extract|\Robo\Collection\CollectionBuilder
*/
protected function taskExtract($filename)
{
return $this->task(Extract::class, $filename);
}
}
<?php
namespace Robo\Task\Archive;
use Robo\Contract\PrintedInterface;
use Robo\Result;
use Robo\Task\BaseTask;
use Symfony\Component\Finder\Finder;
/**
* Creates a zip or tar archive.
*
* ``` php
* <?php
* $this->taskPack(
* <archiveFile>)
* ->add('README') // Puts file 'README' in archive at the root
* ->add('project') // Puts entire contents of directory 'project' in archinve inside 'project'
* ->addFile('dir/file.txt', 'file.txt') // Takes 'file.txt' from cwd and puts it in archive inside 'dir'.
* ->run();
* ?>
* ```
*/
class Pack extends BaseTask implements PrintedInterface
{
/**
* The list of items to be packed into the archive.
*
* @var array
*/
private $items = [];
/**
* The full path to the archive to be created.
*
* @var string
*/
private $archiveFile;
/**
* Construct the class.
*
* @param string $archiveFile
* The full path and name of the archive file to create.
*
* @since 1.0
*/
public function __construct($archiveFile)
{
$this->archiveFile = $archiveFile;
}
/**
* Satisfy the parent requirement.
*
* @return bool
* Always returns true.
*
* @since 1.0
*/
public function getPrinted()
{
return true;
}
/**
* @param string $archiveFile
*
* @return $this
*/
public function archiveFile($archiveFile)
{
$this->archiveFile = $archiveFile;
return $this;
}
/**
* Add an item to the archive. Like file_exists(), the parameter
* may be a file or a directory.
*
* @param string $placementLocation
* Relative path and name of item to store in archive.
* @param string $filesystemLocation
* Absolute or relative path to file or directory's location in filesystem.
*
* @return $this
*/
public function addFile($placementLocation, $filesystemLocation)
{
$this->items[$placementLocation] = $filesystemLocation;
return $this;
}
/**
* Alias for addFile, in case anyone has angst about using
* addFile with a directory.
*
* @param string $placementLocation
* Relative path and name of directory to store in archive.
* @param string $filesystemLocation
* Absolute or relative path to directory or directory's location in filesystem.
*
* @return $this
*/
public function addDir($placementLocation, $filesystemLocation)
{
$this->addFile($placementLocation, $filesystemLocation);
return $this;
}
/**
* Add a file or directory, or list of same to the archive.
*
* @param string|array $item
* If given a string, should contain the relative filesystem path to the
* the item to store in archive; this will also be used as the item's
* path in the archive, so absolute paths should not be used here.
* If given an array, the key of each item should be the path to store
* in the archive, and the value should be the filesystem path to the
* item to store.
*
* @return $this
*/
public function add($item)
{
if (is_array($item)) {
$this->items = array_merge($this->items, $item);
} else {
$this->addFile($item, $item);
}
return $this;
}
/**
* Create a zip archive for distribution.
*
* @return \Robo\Result
*
* @since 1.0
*/
public function run()
{
$this->startTimer();
// Use the file extension to determine what kind of archive to create.
$fileInfo = new \SplFileInfo($this->archiveFile);
$extension = strtolower($fileInfo->getExtension());
if (empty($extension)) {
return Result::error($this, "Archive filename must use an extension (e.g. '.zip') to specify the kind of archive to create.");
}
try {
// Inform the user which archive we are creating
$this->printTaskInfo("Creating archive {filename}", ['filename' => $this->archiveFile]);
if ($extension == 'zip') {
$result = $this->archiveZip($this->archiveFile, $this->items);
} else {
$result = $this->archiveTar($this->archiveFile, $this->items);
}
$this->printTaskSuccess("{filename} created.", ['filename' => $this->archiveFile]);
} catch (\Exception $e) {
$this->printTaskError("Could not create {filename}. {exception}", ['filename' => $this->archiveFile, 'exception' => $e->getMessage(), '_style' => ['exception' => '']]);
$result = Result::error($this, sprintf('Could not create %s. %s', $this->archiveFile, $e->getMessage()));
}
$this->stopTimer();
$result['time'] = $this->getExecutionTime();
return $result;
}
/**
* @param string $archiveFile
* @param array $items
*
* @return \Robo\Result
*/
protected function archiveTar($archiveFile, $items)
{
if (!class_exists('Archive_Tar')) {
return Result::errorMissingPackage($this, 'Archive_Tar', 'pear/archive_tar');
}
$tar_object = new \Archive_Tar($archiveFile);
foreach ($items as $placementLocation => $filesystemLocation) {
$p_remove_dir = $filesystemLocation;
$p_add_dir = $placementLocation;
if (is_file($filesystemLocation)) {
$p_remove_dir = dirname($filesystemLocation);
$p_add_dir = dirname($placementLocation);
if (basename($filesystemLocation) != basename($placementLocation)) {
return Result::error($this, "Tar archiver does not support renaming files during extraction; could not add $filesystemLocation as $placementLocation.");
}
}
if (!$tar_object->addModify([$filesystemLocation], $p_add_dir, $p_remove_dir)) {
return Result::error($this, "Could not add $filesystemLocation to the archive.");
}
}
return Result::success($this);
}
/**
* @param string $archiveFile
* @param array $items
*
* @return \Robo\Result
*/
protected function archiveZip($archiveFile, $items)
{
if (!extension_loaded('zlib') || !class_exists(\ZipArchive::class)) {
return Result::errorMissingExtension($this, 'zlib', 'zip packing');
}
$zip = new \ZipArchive($archiveFile, \ZipArchive::CREATE);
if (!$zip->open($archiveFile, \ZipArchive::CREATE)) {
return Result::error($this, "Could not create zip archive {$archiveFile}");
}
$result = $this->addItemsToZip($zip, $items);
$zip->close();
return $result;
}
/**
* @param \ZipArchive $zip
* @param array $items
*
* @return \Robo\Result
*/
protected function addItemsToZip($zip, $items)
{
foreach ($items as $placementLocation => $filesystemLocation) {
if (is_dir($filesystemLocation)) {
$finder = new Finder();
$finder->files()->in($filesystemLocation)->ignoreDotFiles(false);
foreach ($finder as $file) {
// Replace Windows slashes or resulting zip will have issues on *nixes.
$relativePathname = str_replace('\\', '/', $file->getRelativePathname());
if (!$zip->addFile($file->getRealpath(), "{$placementLocation}/{$relativePathname}")) {
return Result::error($this, "Could not add directory $filesystemLocation to the archive; error adding {$file->getRealpath()}.");
}
}
} elseif (is_file($filesystemLocation)) {
if (!$zip->addFile($filesystemLocation, $placementLocation)) {
return Result::error($this, "Could not add file $filesystemLocation to the archive.");
}
} else {
return Result::error($this, "Could not find $filesystemLocation for the archive.");
}
}
return Result::success($this);
}
}
<?php
namespace Robo\Task\Gulp;
use Robo\Contract\CommandInterface;
/**
* Gulp Run
*
* ``` php
* <?php
* // simple execution
* $this->taskGulpRun()->run();
*
* // run task 'clean' with --silent option
* $this->taskGulpRun('clean')
* ->silent()
* ->run();
* ?>
* ```
*/
class Run extends Base implements CommandInterface
{
/**
* {@inheritdoc}
*/
public function run()
{
if (strlen($this->arguments)) {
$this->printTaskInfo('Running Gulp task: {gulp_task} with arguments: {arguments}', ['gulp_task' => $this->task, 'arguments' => $this->arguments]);
} else {
$this->printTaskInfo('Running Gulp task: {gulp_task} without arguments', ['gulp_task' => $this->task]);
}
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Gulp;
use Robo\Task\BaseTask;
use Robo\Exception\TaskException;
use Robo\Common\ProcessUtils;
abstract class Base extends BaseTask
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command = '';
/**
* @var array
*/
protected $opts = [];
/**
* @var string
*/
protected $task = '';
/**
* adds `silent` option to gulp
*
* @return $this
*/
public function silent()
{
$this->option('silent');
return $this;
}
/**
* adds `--no-color` option to gulp
*
* @return $this
*/
public function noColor()
{
$this->option('no-color');
return $this;
}
/**
* adds `--color` option to gulp
*
* @return $this
*/
public function color()
{
$this->option('color');
return $this;
}
/**
* adds `--tasks-simple` option to gulp
*
* @return $this
*/
public function simple()
{
$this->option('tasks-simple');
return $this;
}
/**
* @param string $task
* @param null|string $pathToGulp
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($task, $pathToGulp = null)
{
$this->task = $task;
$this->command = $pathToGulp;
if (!$this->command) {
$this->command = $this->findExecutable('gulp');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "Gulp executable not found.");
}
}
/**
* @return string
*/
public function getCommand()
{
return "{$this->command} " . ProcessUtils::escapeArgument($this->task) . "{$this->arguments}";
}
}
<?php
namespace Robo\Task\Gulp;
trait loadTasks
{
/**
* @param string $task
* @param null|string $pathToGulp
*
* @return \Robo\Task\Gulp\Run|\Robo\Collection\CollectionBuilder
*/
protected function taskGulpRun($task = 'default', $pathToGulp = null)
{
return $this->task(Run::class, $task, $pathToGulp);
}
}
<?php
namespace Robo\Task;
use Robo\Common\ExecCommand;
use Robo\Contract\PrintedInterface;
use Robo\Result;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
abstract class CommandStack extends BaseTask implements CommandInterface, PrintedInterface
{
use ExecCommand;
use \Robo\Common\CommandReceiver;
/**
* @var string
*/
protected $executable;
/**
* @var \Robo\Result
*/
protected $result;
/**
* @var string[]
*/
protected $exec = [];
/**
* @var bool
*/
protected $stopOnFail = false;
/**
* {@inheritdoc}
*/
public function getCommand()
{
$commands = [];
foreach ($this->exec as $command) {
$commands[] = $this->receiveCommand($command);
}
return implode(' && ', $commands);
}
/**
* @param string $executable
*
* @return $this
*/
public function executable($executable)
{
$this->executable = $executable;
return $this;
}
/**
* @param string|string[]|CommandInterface $command
*
* @return $this
*/
public function exec($command)
{
if (is_array($command)) {
$command = implode(' ', array_filter($command));
}
if (is_string($command)) {
$command = $this->executable . ' ' . $this->stripExecutableFromCommand($command);
$command = trim($command);
}
$this->exec[] = $command;
return $this;
}
/**
* @param bool $stopOnFail
*
* @return $this
*/
public function stopOnFail($stopOnFail = true)
{
$this->stopOnFail = $stopOnFail;
return $this;
}
public function result($result)
{
$this->result = $result;
return $this;
}
/**
* @param string $command
*
* @return string
*/
protected function stripExecutableFromCommand($command)
{
$command = trim($command);
$executable = $this->executable . ' ';
if (strpos($command, $executable) === 0) {
$command = substr($command, strlen($executable));
}
return $command;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (empty($this->exec)) {
throw new TaskException($this, 'You must add at least one command');
}
// If 'stopOnFail' is not set, or if there is only one command to run,
// then execute the single command to run.
if (!$this->stopOnFail || (count($this->exec) == 1)) {
$this->printTaskInfo('{command}', ['command' => $this->getCommand()]);
return $this->executeCommand($this->getCommand());
}
// When executing multiple commands in 'stopOnFail' mode, run them
// one at a time so that the result will have the exact command
// that failed available to the caller. This is at the expense of
// losing the output from all successful commands.
$data = [];
$message = '';
$result = null;
foreach ($this->exec as $command) {
$this->printTaskInfo("Executing {command}", ['command' => $command]);
$result = $this->executeCommand($command);
$result->accumulateExecutionTime($data);
$message = $result->accumulateMessage($message);
$data = $result->mergeData($data);
if (!$result->wasSuccessful()) {
return $result;
}
}
return $result;
}
}
<?php
namespace Robo\Task\Bower;
/**
* Bower Update
*
* ``` php
* <?php
* // simple execution
* $this->taskBowerUpdate->run();
*
* // prefer dist with custom path
* $this->taskBowerUpdate('path/to/my/bower')
* ->noDev()
* ->run();
* ?>
* ```
*/
class Update extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'update';
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Update Bower packages: {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Bower;
use Robo\Contract\CommandInterface;
/**
* Bower Install
*
* ``` php
* <?php
* // simple execution
* $this->taskBowerInstall()->run();
*
* // prefer dist with custom path
* $this->taskBowerInstall('path/to/my/bower')
* ->noDev()
* ->run();
* ?>
* ```
*/
class Install extends Base implements CommandInterface
{
/**
* {@inheritdoc}
*/
protected $action = 'install';
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Install Bower packages: {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Bower;
use Robo\Task\BaseTask;
use Robo\Exception\TaskException;
abstract class Base extends BaseTask
{
use \Robo\Common\ExecOneCommand;
/**
* @var array
*/
protected $opts = [];
/**
* @var string
*/
protected $action = '';
/**
* @var string
*/
protected $command = '';
/**
* adds `allow-root` option to bower
*
* @return $this
*/
public function allowRoot()
{
$this->option('allow-root');
return $this;
}
/**
* adds `force-latest` option to bower
*
* @return $this
*/
public function forceLatest()
{
$this->option('force-latest');
return $this;
}
/**
* adds `production` option to bower
*
* @return $this
*/
public function noDev()
{
$this->option('production');
return $this;
}
/**
* adds `offline` option to bower
*
* @return $this
*/
public function offline()
{
$this->option('offline');
return $this;
}
/**
* Base constructor.
*
* @param null|string $pathToBower
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToBower = null)
{
$this->command = $pathToBower;
if (!$this->command) {
$this->command = $this->findExecutable('bower');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "Bower executable not found.");
}
}
/**
* @return string
*/
public function getCommand()
{
return "{$this->command} {$this->action}{$this->arguments}";
}
}
<?php
namespace Robo\Task\Bower;
trait loadTasks
{
/**
* @param null|string $pathToBower
*
* @return \Robo\Task\Bower\Install|\Robo\Collection\CollectionBuilder
*/
protected function taskBowerInstall($pathToBower = null)
{
return $this->task(Install::class, $pathToBower);
}
/**
* @param null|string $pathToBower
*
* @return \Robo\Task\Bower\Update|\Robo\Collection\CollectionBuilder
*/
protected function taskBowerUpdate($pathToBower = null)
{
return $this->task(Update::class, $pathToBower);
}
}
<?php
namespace Robo\Task\File;
use Robo\Contract\CompletionInterface;
/**
* Create a temporary file that is automatically cleaned up
* once the task collection is is part of completes. When created,
* it is given a random filename.
*
* This temporary file may be manipulated exacatly like taskWrite().
* It is deleted as soon as the collection it is a part of completes
* or rolls back.
*
* ``` php
* <?php
* $collection = $this->collectionBuilder();
* $tmpFilePath = $collection->taskTmpFile()
* ->line('-----')
* ->line(date('Y-m-d').' '.$title)
* ->line('----')
* ->getPath();
* $collection->run();
* ?>
* ```
*/
class TmpFile extends Write implements CompletionInterface
{
/**
* @param string $filename
* @param string $extension
* @param string $baseDir
* @param bool $includeRandomPart
*/
public function __construct($filename = 'tmp', $extension = '', $baseDir = '', $includeRandomPart = true)
{
if (empty($baseDir)) {
$baseDir = sys_get_temp_dir();
}
if ($includeRandomPart) {
$random = static::randomString();
$filename = "{$filename}_{$random}";
}
$filename .= $extension;
parent::__construct("{$baseDir}/{$filename}");
}
/**
* Generate a suitably random string to use as the suffix for our
* temporary file.
*
* @param int $length
*
* @return string
*/
private static function randomString($length = 12)
{
return substr(str_shuffle('23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'), 0, $length);
}
/**
* Delete this file when our collection completes.
* If this temporary file is not part of a collection,
* then it will be deleted when the program terminates,
* presuming that it was created by taskTmpFile() or _tmpFile().
*/
public function complete()
{
unlink($this->getPath());
}
}
<?php
namespace Robo\Task\File;
use Robo\Result;
use Robo\Task\BaseTask;
/**
* Performs search and replace inside a files.
*
* ``` php
* <?php
* $this->taskReplaceInFile('VERSION')
* ->from('0.2.0')
* ->to('0.3.0')
* ->run();
*
* $this->taskReplaceInFile('README.md')
* ->from(date('Y')-1)
* ->to(date('Y'))
* ->run();
*
* $this->taskReplaceInFile('config.yml')
* ->regex('~^service:~')
* ->to('services:')
* ->run();
*
* $this->taskReplaceInFile('box/robo.txt')
* ->from(array('##dbname##', '##dbhost##'))
* ->to(array('robo', 'localhost'))
* ->run();
* ?>
* ```
*/
class Replace extends BaseTask
{
/**
* @var string
*/
protected $filename;
/**
* @var string|string[]
*/
protected $from;
/**
* @var string|string[]
*/
protected $to;
/**
* @var string
*/
protected $regex;
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* @param string $filename
*
* @return $this
*/
public function filename($filename)
{
$this->filename = $filename;
return $this;
}
/**
* String(s) to be replaced.
*
* @param string|string[] $from
*
* @return $this
*/
public function from($from)
{
$this->from = $from;
return $this;
}
/**
* Value(s) to be set as a replacement.
*
* @param string|string[] $to
*
* @return $this
*/
public function to($to)
{
$this->to = $to;
return $this;
}
/**
* Regex to match string to be replaced.
*
* @param string $regex
*
* @return $this
*/
public function regex($regex)
{
$this->regex = $regex;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (!file_exists($this->filename)) {
$this->printTaskError('File {filename} does not exist', ['filename' => $this->filename]);
return false;
}
$text = file_get_contents($this->filename);
if ($this->regex) {
$text = preg_replace($this->regex, $this->to, $text, -1, $count);
} else {
$text = str_replace($this->from, $this->to, $text, $count);
}
if ($count > 0) {
$res = file_put_contents($this->filename, $text);
if ($res === false) {
return Result::error($this, "Error writing to file {filename}.", ['filename' => $this->filename]);
}
$this->printTaskSuccess("{filename} updated. {count} items replaced", ['filename' => $this->filename, 'count' => $count]);
} else {
$this->printTaskInfo("{filename} unchanged. {count} items replaced", ['filename' => $this->filename, 'count' => $count]);
}
return Result::success($this, '', ['replaced' => $count]);
}
}
<?php
namespace Robo\Task\File;
use Iterator;
use Robo\Common\ResourceExistenceChecker;
use Robo\Result;
use Robo\Task\BaseTask;
/**
* Merges files into one. Used for preparing assets.
*
* ``` php
* <?php
* $this->taskConcat([
* 'web/assets/screen.css',
* 'web/assets/print.css',
* 'web/assets/theme.css'
* ])
* ->to('web/assets/style.css')
* ->run()
* ?>
* ```
*/
class Concat extends BaseTask
{
use ResourceExistenceChecker;
/**
* @var array|\Iterator
*/
protected $files;
/**
* @var string
*/
protected $dst;
/**
* Constructor.
*
* @param array|\Iterator $files
*/
public function __construct($files)
{
$this->files = $files;
}
/**
* set the destination file
*
* @param string $dst
*
* @return $this
*/
public function to($dst)
{
$this->dst = $dst;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (is_null($this->dst) || "" === $this->dst) {
return Result::error($this, 'You must specify a destination file with to() method.');
}
if (!$this->checkResources($this->files, 'file')) {
return Result::error($this, 'Source files are missing!');
}
if (file_exists($this->dst) && !is_writable($this->dst)) {
return Result::error($this, 'Destination already exists and cannot be overwritten.');
}
$dump = '';
foreach ($this->files as $path) {
foreach (glob($path) as $file) {
$dump .= file_get_contents($file) . "\n";
}
}
$this->printTaskInfo('Writing {destination}', ['destination' => $this->dst]);
$dst = $this->dst . '.part';
$write_result = file_put_contents($dst, $dump);
if (false === $write_result) {
@unlink($dst);
return Result::error($this, 'File write failed.');
}
// Cannot be cross-volume; should always succeed.
@rename($dst, $this->dst);
return Result::success($this);
}
}
<?php
namespace Robo\Task\File;
trait loadTasks
{
/**
* @param array|\Iterator $files
*
* @return \Robo\Task\File\Concat|\Robo\Collection\CollectionBuilder
*/
protected function taskConcat($files)
{
return $this->task(Concat::class, $files);
}
/**
* @param string $file
*
* @return \Robo\Task\File\Replace|\Robo\Collection\CollectionBuilder
*/
protected function taskReplaceInFile($file)
{
return $this->task(Replace::class, $file);
}
/**
* @param string $file
*
* @return \Robo\Task\File\Write|\Robo\Collection\CollectionBuilder
*/
protected function taskWriteToFile($file)
{
return $this->task(Write::class, $file);
}
/**
* @param string $filename
* @param string $extension
* @param string $baseDir
* @param bool $includeRandomPart
*
* @return \Robo\Task\File\TmpFile|\Robo\Collection\CollectionBuilder
*/
protected function taskTmpFile($filename = 'tmp', $extension = '', $baseDir = '', $includeRandomPart = true)
{
return $this->task(TmpFile::class, $filename, $extension, $baseDir, $includeRandomPart);
}
}
<?php
namespace Robo\Task\File;
use Robo\Result;
use Robo\Task\BaseTask;
/**
* Writes to file.
*
* ``` php
* <?php
* $this->taskWriteToFile('blogpost.md')
* ->line('-----')
* ->line(date('Y-m-d').' '.$title)
* ->line('----')
* ->run();
* ?>
* ```
*/
class Write extends BaseTask
{
/**
* @var array
*/
protected $stack = [];
/**
* @var string
*/
protected $filename;
/**
* @var bool
*/
protected $append = false;
/**
* @var null|string
*/
protected $originalContents = null;
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* @param string $filename
*
* @return $this
*/
public function filename($filename)
{
$this->filename = $filename;
return $this;
}
/**
* @param bool $append
*
* @return $this
*/
public function append($append = true)
{
$this->append = $append;
return $this;
}
/**
* add a line.
*
* @param string $line
*
* @return $this
* The current instance.
*/
public function line($line)
{
$this->text($line . "\n");
return $this;
}
/**
* add more lines.
*
* @param array $lines
*
* @return $this
* The current instance.
*/
public function lines(array $lines)
{
$this->text(implode("\n", $lines) . "\n");
return $this;
}
/**
* add a text.
*
* @param string $text
*
* @return $this
* The current instance.
*/
public function text($text)
{
$this->stack[] = array_merge([__FUNCTION__ . 'Collect'], func_get_args());
return $this;
}
/**
* add a text from a file.
*
* Note that the file is read in the run() method of this task.
* To load text from the current state of a file (e.g. one that may
* be deleted or altered by other tasks prior the execution of this one),
* use:
* $task->text(file_get_contents($filename));
*
* @param string $filename
*
* @return $this
* The current instance.
*/
public function textFromFile($filename)
{
$this->stack[] = array_merge([__FUNCTION__ . 'Collect'], func_get_args());
return $this;
}
/**
* substitute a placeholder with value, placeholder must be enclosed by `{}`.
*
* @param string $name
* @param string $val
*
* @return $this
* The current instance.
*/
public function place($name, $val)
{
$this->replace('{' . $name . '}', $val);
return $this;
}
/**
* replace any string with value.
*
* @param string $string
* @param string $replacement
*
* @return $this
* The current instance.
*/
public function replace($string, $replacement)
{
$this->stack[] = array_merge([__FUNCTION__ . 'Collect'], func_get_args());
return $this;
}
/**
* replace any string with value using regular expression.
*
* @param string $pattern
* @param string $replacement
*
* @return $this
* The current instance.
*/
public function regexReplace($pattern, $replacement)
{
$this->stack[] = array_merge([__FUNCTION__ . 'Collect'], func_get_args());
return $this;
}
/**
* Append the provided text to the end of the buffer if the provided
* regex pattern matches any text already in the buffer.
*
* @param string $pattern
* @param string $text
*
* @return $this
*/
public function appendIfMatches($pattern, $text)
{
$this->stack[] = array_merge(['appendIfMatchesCollect'], [$pattern, $text, true]);
return $this;
}
/**
* Append the provided text to the end of the buffer unless the provided
* regex pattern matches any text already in the buffer.
*
* @param string $pattern
* @param string $text
*
* @return $this
*/
public function appendUnlessMatches($pattern, $text)
{
$this->stack[] = array_merge(['appendIfMatchesCollect'], [$pattern, $text, false]);
return $this;
}
/**
* @param string $contents
* @param string $filename
*
* @return string
*/
protected function textFromFileCollect($contents, $filename)
{
if (file_exists($filename)) {
$contents .= file_get_contents($filename);
}
return $contents;
}
/**
* @param string|string[] $contents
* @param string|string[] $string
* @param string|string[] $replacement
*
* @return string|string[]
*/
protected function replaceCollect($contents, $string, $replacement)
{
return str_replace($string, $replacement, $contents);
}
/**
* @param string|string[] $contents
* @param string|string[] $pattern
* @param string|string[] $replacement
*
* @return string|string[]
*/
protected function regexReplaceCollect($contents, $pattern, $replacement)
{
return preg_replace($pattern, $replacement, $contents);
}
/**
* @param string $contents
* @param string $text
*
* @return string
*/
protected function textCollect($contents, $text)
{
return $contents . $text;
}
/**
* @param string $contents
* @param string $pattern
* @param string $text
* @param bool $shouldMatch
*
* @return string
*/
protected function appendIfMatchesCollect($contents, $pattern, $text, $shouldMatch)
{
if (preg_match($pattern, $contents) == $shouldMatch) {
$contents .= $text;
}
return $contents;
}
/**
* @return string
*/
public function originalContents()
{
if (!isset($this->originalContents)) {
$this->originalContents = '';
if (file_exists($this->filename)) {
$this->originalContents = file_get_contents($this->filename);
}
}
return $this->originalContents;
}
/**
* @return bool
*/
public function wouldChange()
{
return $this->originalContents() != $this->getContentsToWrite();
}
/**
* @return string
*/
protected function getContentsToWrite()
{
$contents = "";
if ($this->append) {
$contents = $this->originalContents();
}
foreach ($this->stack as $action) {
$command = array_shift($action);
if (method_exists($this, $command)) {
array_unshift($action, $contents);
$contents = call_user_func_array([$this, $command], $action);
}
}
return $contents;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo("Writing to {filename}.", ['filename' => $this->filename]);
$contents = $this->getContentsToWrite();
if (!file_exists(dirname($this->filename))) {
mkdir(dirname($this->filename), 0777, true);
}
$res = file_put_contents($this->filename, $contents);
if ($res === false) {
return Result::error($this, "File {$this->filename} couldn't be created");
}
return Result::success($this);
}
/**
* @return string
*/
public function getPath()
{
return $this->filename;
}
}
<?php
namespace Robo\Task\Development;
use Robo\Task\Base\Exec;
/**
* Runs PHP server and stops it when task finishes.
*
* ``` php
* <?php
* // run server in /public directory
* $this->taskServer(8000)
* ->dir('public')
* ->run();
*
* // run with IP 0.0.0.0
* $this->taskServer(8000)
* ->host('0.0.0.0')
* ->run();
*
* // execute server in background
* $this->taskServer(8000)
* ->background()
* ->run();
* ?>
* ```
*/
class PhpServer extends Exec
{
/**
* @var int
*/
protected $port;
/**
* @var string
*/
protected $host = '127.0.0.1';
/**
* {@inheritdoc}
*/
protected $command = 'php -S %s:%d ';
/**
* @param int $port
*/
public function __construct($port)
{
$this->port = $port;
if (strtolower(PHP_OS) === 'linux') {
$this->command = 'exec php -S %s:%d ';
}
}
/**
* @param string $host
*
* @return $this
*/
public function host($host)
{
$this->host = $host;
return $this;
}
/**
* @param string $path
*
* @return $this
*/
public function dir($path)
{
$this->command .= "-t $path";
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return sprintf($this->command . $this->arguments, $this->host, $this->port);
}
}
<?php
namespace Robo\Task\Development;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
abstract class GitHub extends BaseTask
{
const GITHUB_URL = 'https://api.github.com';
/**
* @var string
*/
protected $user = '';
/**
* @var string
*/
protected $password = '';
/**
* @var string
*/
protected $repo;
/**
* @var string
*/
protected $owner;
/**
* @var string
*/
protected $accessToken;
/**
* @param string $repo
*
* @return $this
*/
public function repo($repo)
{
$this->repo = $repo;
return $this;
}
/**
* @param string $owner
*
* @return $this
*/
public function owner($owner)
{
$this->owner = $owner;
return $this;
}
/**
* @param string $uri
*
* @return $this
*/
public function uri($uri)
{
list($this->owner, $this->repo) = explode('/', $uri);
return $this;
}
/**
* @return string
*/
protected function getUri()
{
return $this->owner . '/' . $this->repo;
}
/**
* @param string $user
*
* @return $this
*/
public function user($user)
{
$this->user = $user;
return $this;
}
/**
* @param string $password
*
* @return $this
*/
public function password($password)
{
$this->password = $password;
return $this;
}
/**
* @param string $token
*
* @return $this
*/
public function accessToken($token)
{
$this->accessToken = $token;
return $this;
}
/**
* @param string $uri
* @param array $params
* @param string $method
*
* @return array
*
* @throws \Robo\Exception\TaskException
*/
protected function sendRequest($uri, $params = [], $method = 'POST')
{
if (!$this->owner or !$this->repo) {
throw new TaskException($this, 'Repo URI is not set');
}
$ch = curl_init();
$url = sprintf('%s/repos/%s/%s', self::GITHUB_URL, $this->getUri(), $uri);
$this->printTaskInfo($url);
$this->printTaskInfo('{method} {url}', ['method' => $method, 'url' => $url]);
if (!empty($this->user)) {
curl_setopt($ch, CURLOPT_USERPWD, $this->user . ':' . $this->password);
}
if (!empty($this->accessToken)) {
$url .= "?access_token=" . $this->accessToken;
}
curl_setopt_array(
$ch,
array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => $method != 'GET',
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_USERAGENT => "Robo"
)
);
$output = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$response = json_decode($output);
$this->printTaskInfo($output);
return [$code, $response];
}
}
<?php
namespace Robo\Task\Development;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Contract\PrintedInterface;
use Robo\Result;
use Robo\Task\BaseTask;
/**
* Creates Phar.
*
* ``` php
* <?php
* $pharTask = $this->taskPackPhar('package/codecept.phar')
* ->compress()
* ->stub('package/stub.php');
*
* $finder = Finder::create()
* ->name('*.php')
* ->in('src');
*
* foreach ($finder as $file) {
* $pharTask->addFile('src/'.$file->getRelativePathname(), $file->getRealPath());
* }
*
* $finder = Finder::create()->files()
* ->name('*.php')
* ->in('vendor');
*
* foreach ($finder as $file) {
* $pharTask->addStripped('vendor/'.$file->getRelativePathname(), $file->getRealPath());
* }
* $pharTask->run();
*
* // verify Phar is packed correctly
* $code = $this->_exec('php package/codecept.phar');
* ?>
* ```
*/
class PackPhar extends BaseTask implements PrintedInterface, ProgressIndicatorAwareInterface
{
/**
* @var \Phar
*/
protected $phar;
/**
* @var null|string
*/
protected $compileDir = null;
/**
* @var string
*/
protected $filename;
/**
* @var bool
*/
protected $compress = false;
protected $stub;
protected $bin;
/**
* @var string
*/
protected $stubTemplate = <<<EOF
#!/usr/bin/env php
<?php
Phar::mapPhar();
%s
__HALT_COMPILER();
EOF;
/**
* @var string[]
*/
protected $files = [];
/**
* {@inheritdoc}
*/
public function getPrinted()
{
return true;
}
/**
* @param string $filename
*/
public function __construct($filename)
{
$file = new \SplFileInfo($filename);
$this->filename = $filename;
if (file_exists($file->getRealPath())) {
@unlink($file->getRealPath());
}
$this->phar = new \Phar($file->getPathname(), 0, $file->getFilename());
}
/**
* @param bool $compress
*
* @return $this
*/
public function compress($compress = true)
{
$this->compress = $compress;
return $this;
}
/**
* @param string $stub
*
* @return $this
*/
public function stub($stub)
{
$this->phar->setStub(file_get_contents($stub));
return $this;
}
/**
* {@inheritdoc}
*/
public function progressIndicatorSteps()
{
// run() will call advanceProgressIndicator() once for each
// file, one after calling stopBuffering, and again after compression.
return count($this->files) + 2;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Creating {filename}', ['filename' => $this->filename]);
$this->phar->setSignatureAlgorithm(\Phar::SHA1);
$this->phar->startBuffering();
$this->printTaskInfo('Packing {file-count} files into phar', ['file-count' => count($this->files)]);
$this->startProgressIndicator();
foreach ($this->files as $path => $content) {
$this->phar->addFromString($path, $content);
$this->advanceProgressIndicator();
}
$this->phar->stopBuffering();
$this->advanceProgressIndicator();
if ($this->compress and in_array('GZ', \Phar::getSupportedCompression())) {
if (count($this->files) > 1000) {
$this->printTaskInfo('Too many files. Compression DISABLED');
} else {
$this->printTaskInfo('{filename} compressed', ['filename' => $this->filename]);
$this->phar = $this->phar->compressFiles(\Phar::GZ);
}
}
$this->advanceProgressIndicator();
$this->stopProgressIndicator();
$this->printTaskSuccess('{filename} produced', ['filename' => $this->filename]);
return Result::success($this, '', ['time' => $this->getExecutionTime()]);
}
/**
* @param string $path
* @param string $file
*
* @return $this
*/
public function addStripped($path, $file)
{
$this->files[$path] = $this->stripWhitespace(file_get_contents($file));
return $this;
}
/**
* @param string $path
* @param string $file
*
* @return $this
*/
public function addFile($path, $file)
{
$this->files[$path] = file_get_contents($file);
return $this;
}
/**
* @param \Symfony\Component\Finder\SplFileInfo[] $files
*/
public function addFiles($files)
{
foreach ($files as $file) {
$this->addFile($file->getRelativePathname(), $file->getRealPath());
}
}
/**
* @param string $file
*
* @return $this
*/
public function executable($file)
{
$source = file_get_contents($file);
if (strpos($source, '#!/usr/bin/env php') === 0) {
$source = substr($source, strpos($source, '<?php') + 5);
}
$this->phar->setStub(sprintf($this->stubTemplate, $source));
return $this;
}
/**
* Strips whitespace from source. Taken from composer
*
* @param string $source
*
* @return string
*/
private function stripWhitespace($source)
{
if (!function_exists('token_get_all')) {
return $source;
}
$output = '';
foreach (token_get_all($source) as $token) {
if (is_string($token)) {
$output .= $token;
} elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
// $output .= $token[1];
$output .= str_repeat("\n", substr_count($token[1], "\n"));
} elseif (T_WHITESPACE === $token[0]) {
// reduce wide spaces
$whitespace = preg_replace('{[ \t]+}', ' ', $token[1]);
// normalize newlines to \n
$whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
// trim leading spaces
$whitespace = preg_replace('{\n +}', "\n", $whitespace);
$output .= $whitespace;
} else {
$output .= $token[1];
}
}
return $output;
}
}
<?php
namespace Robo\Task\Development;
use Robo\Task\BaseTask;
use Robo\Common\ProcessUtils;
use Robo\Result;
/**
* Opens the default's user browser
* code inspired from openBrowser() function in https://github.com/composer/composer/blob/master/src/Composer/Command/HomeCommand.php
*
* ``` php
* <?php
* // open one browser window
* $this->taskOpenBrowser('http://localhost')
* ->run();
*
* // open two browser windows
* $this->taskOpenBrowser([
* 'http://localhost/mysite',
* 'http://localhost/mysite2'
* ])
* ->run();
* ```
*/
class OpenBrowser extends BaseTask
{
/**
* @var string[]
*/
protected $urls = [];
/**
* @param string|string[] $url
*/
public function __construct($url)
{
$this->urls = (array) $url;
}
/**
* {@inheritdoc}
*/
public function run()
{
$openCommand = $this->getOpenCommand();
if (empty($openCommand)) {
return Result::error($this, 'no suitable browser opening command found');
}
foreach ($this->urls as $url) {
passthru(sprintf($openCommand, ProcessUtils::escapeArgument($url)));
$this->printTaskInfo('Opened {url}', ['url' => $url]);
}
return Result::success($this);
}
/**
* @return null|string
*/
private function getOpenCommand()
{
if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
return 'start "web" explorer "%s"';
}
passthru('which xdg-open', $linux);
passthru('which open', $osx);
if (0 === $linux) {
return 'xdg-open %s';
}
if (0 === $osx) {
return 'open %s';
}
}
}
<?php
namespace Robo\Task\Development;
trait loadTasks
{
/**
* @param string $filename
*
* @return \Robo\Task\Development\Changelog|\Robo\Collection\CollectionBuilder
*/
protected function taskChangelog($filename = 'CHANGELOG.md')
{
return $this->task(Changelog::class, $filename);
}
/**
* @param string $filename
*
* @return \Robo\Task\Development\GenerateMarkdownDoc|\Robo\Collection\CollectionBuilder
*/
protected function taskGenDoc($filename)
{
return $this->task(GenerateMarkdownDoc::class, $filename);
}
/**
* @param string $className
* @param string $wrapperClassName
*
* @return \Robo\Task\Development\GenerateTask|\Robo\Collection\CollectionBuilder
*/
protected function taskGenTask($className, $wrapperClassName = '')
{
return $this->task(GenerateTask::class, $className, $wrapperClassName);
}
/**
* @param string $pathToSemVer
*
* @return \Robo\Task\Development\SemVer|\Robo\Collection\CollectionBuilder
*/
protected function taskSemVer($pathToSemVer = '.semver')
{
return $this->task(SemVer::class, $pathToSemVer);
}
/**
* @param int $port
*
* @return \Robo\Task\Development\PhpServer|\Robo\Collection\CollectionBuilder
*/
protected function taskServer($port = 8000)
{
return $this->task(PhpServer::class, $port);
}
/**
* @param string $filename
*
* @return \Robo\Task\Development\PackPhar|\Robo\Collection\CollectionBuilder
*/
protected function taskPackPhar($filename)
{
return $this->task(PackPhar::class, $filename);
}
/**
* @param string $tag
*
* @return \Robo\Task\Development\GitHubRelease|\Robo\Collection\CollectionBuilder
*/
protected function taskGitHubRelease($tag)
{
return $this->task(GitHubRelease::class, $tag);
}
/**
* @param string|array $url
*
* @return \Robo\Task\Development\OpenBrowser|\Robo\Collection\CollectionBuilder
*/
protected function taskOpenBrowser($url)
{
return $this->task(OpenBrowser::class, $url);
}
}
<?php
namespace Robo\Task\Development;
use Robo\Result;
/**
* Publishes new GitHub release.
*
* ``` php
* <?php
* $this->taskGitHubRelease('0.1.0')
* ->uri('consolidation-org/Robo')
* ->description('Add stuff people need.')
* ->change('Fix #123')
* ->change('Add frobulation method to all widgets')
* ->run();
* ?>
* ```
*/
class GitHubRelease extends GitHub
{
/**
* @var string
*/
protected $tag;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $description = '';
/**
* @var string[]
*/
protected $changes = [];
/**
* @var bool
*/
protected $draft = false;
/**
* @var bool
*/
protected $prerelease = false;
/**
* @var string
*/
protected $comittish = 'master';
/**
* @param string $tag
*/
public function __construct($tag)
{
$this->tag = $tag;
}
/**
* @param string $tag
*
* @return $this
*/
public function tag($tag)
{
$this->tag = $tag;
return $this;
}
/**
* @param bool $draft
*
* @return $this
*/
public function draft($draft)
{
$this->draft = $draft;
return $this;
}
/**
* @param string $name
*
* @return $this
*/
public function name($name)
{
$this->name = $name;
return $this;
}
/**
* @param string $description
*
* @return $this
*/
public function description($description)
{
$this->description = $description;
return $this;
}
/**
* @param bool $prerelease
*
* @return $this
*/
public function prerelease($prerelease)
{
$this->prerelease = $prerelease;
return $this;
}
/**
* @param string $comittish
*
* @return $this
*/
public function comittish($comittish)
{
$this->comittish = $comittish;
return $this;
}
/**
* @param string $description
*
* @return $this
*/
public function appendDescription($description)
{
if (!empty($this->description)) {
$this->description .= "\n\n";
}
$this->description .= $description;
return $this;
}
public function changes(array $changes)
{
$this->changes = array_merge($this->changes, $changes);
return $this;
}
/**
* @param string $change
*
* @return $this
*/
public function change($change)
{
$this->changes[] = $change;
return $this;
}
/**
* @return string
*/
protected function getBody()
{
$body = $this->description;
if (!empty($this->changes)) {
$changes = array_map(
function ($line) {
return "* $line";
},
$this->changes
);
$changesText = implode("\n", $changes);
$body .= "### Changelog \n\n$changesText";
}
return $body;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Releasing {tag}', ['tag' => $this->tag]);
$this->startTimer();
list($code, $data) = $this->sendRequest(
'releases',
[
"tag_name" => $this->tag,
"target_commitish" => $this->comittish,
"name" => $this->name,
"body" => $this->getBody(),
"draft" => $this->draft,
"prerelease" => $this->prerelease
]
);
$this->stopTimer();
return new Result(
$this,
in_array($code, [200, 201]) ? 0 : 1,
isset($data->message) ? $data->message : '',
['response' => $data, 'time' => $this->getExecutionTime()]
);
}
}
<?php
namespace Robo\Task\Development;
use Robo\Task\BaseTask;
use Robo\Result;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Simple documentation generator from source files.
* Takes classes, properties and methods with their docblocks and writes down a markdown file.
*
* ``` php
* <?php
* $this->taskGenDoc('models.md')
* ->docClass('Model\User') // take class Model\User
* ->docClass('Model\Post') // take class Model\Post
* ->filterMethods(function(\ReflectionMethod $r) {
* return $r->isPublic() or $r->isProtected(); // process public and protected methods
* })->processClass(function(\ReflectionClass $r, $text) {
* return "Class ".$r->getName()."\n\n$text\n\n###Methods\n";
* })->run();
* ```
*
* By default this task generates a documentation for each public method of a class, interface or trait.
* It combines method signature with a docblock. Both can be post-processed.
*
* ``` php
* <?php
* $this->taskGenDoc('models.md')
* ->docClass('Model\User')
* ->processClassSignature(false) // false can be passed to not include class signature
* ->processClassDocBlock(function(\ReflectionClass $r, $text) {
* return "[This is part of application model]\n" . $text;
* })->processMethodSignature(function(\ReflectionMethod $r, $text) {
* return "#### {$r->name}()";
* })->processMethodDocBlock(function(\ReflectionMethod $r, $text) {
* return strpos($r->name, 'save')===0 ? "[Saves to the database]\n" . $text : $text;
* })->run();
* ```
*/
class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var string[]
*/
protected $docClass = [];
/**
* @var callable
*/
protected $filterMethods;
/**
* @var callable
*/
protected $filterClasses;
/**
* @var callable
*/
protected $filterProperties;
/**
* @var callable
*/
protected $processClass;
/**
* @var callable|false
*/
protected $processClassSignature;
/**
* @var callable|false
*/
protected $processClassDocBlock;
/**
* @var callable|false
*/
protected $processMethod;
/**
* @var callable|false
*/
protected $processMethodSignature;
/**
* @var callable|false
*/
protected $processMethodDocBlock;
/**
* @var callable|false
*/
protected $processProperty;
/**
* @var callable|false
*/
protected $processPropertySignature;
/**
* @var callable|false
*/
protected $processPropertyDocBlock;
/**
* @var callable
*/
protected $reorder;
/**
* @var callable
*/
protected $reorderMethods;
/**
* @todo Unused property.
*
* @var callable
*/
protected $reorderProperties;
/**
* @var string
*/
protected $filename;
/**
* @var string
*/
protected $prepend = "";
/**
* @var string
*/
protected $append = "";
/**
* @var string
*/
protected $text;
/**
* @var string[]
*/
protected $textForClass = [];
/**
* @param string $filename
*
* @return static
*/
public static function init($filename)
{
return new static($filename);
}
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* Put a class you want to be documented.
*
* @param string $item
*
* @return $this
*/
public function docClass($item)
{
$this->docClass[] = $item;
return $this;
}
/**
* Using a callback function filter out methods that won't be documented.
*
* @param callable $filterMethods
*
* @return $this
*/
public function filterMethods($filterMethods)
{
$this->filterMethods = $filterMethods;
return $this;
}
/**
* Using a callback function filter out classes that won't be documented.
*
* @param callable $filterClasses
*
* @return $this
*/
public function filterClasses($filterClasses)
{
$this->filterClasses = $filterClasses;
return $this;
}
/**
* Using a callback function filter out properties that won't be documented.
*
* @param callable $filterProperties
*
* @return $this
*/
public function filterProperties($filterProperties)
{
$this->filterProperties = $filterProperties;
return $this;
}
/**
* Post-process class documentation.
*
* @param callable $processClass
*
* @return $this
*/
public function processClass($processClass)
{
$this->processClass = $processClass;
return $this;
}
/**
* Post-process class signature. Provide *false* to skip.
*
* @param callable|false $processClassSignature
*
* @return $this
*/
public function processClassSignature($processClassSignature)
{
$this->processClassSignature = $processClassSignature;
return $this;
}
/**
* Post-process class docblock contents. Provide *false* to skip.
*
* @param callable|false $processClassDocBlock
*
* @return $this
*/
public function processClassDocBlock($processClassDocBlock)
{
$this->processClassDocBlock = $processClassDocBlock;
return $this;
}
/**
* Post-process method documentation. Provide *false* to skip.
*
* @param callable|false $processMethod
*
* @return $this
*/
public function processMethod($processMethod)
{
$this->processMethod = $processMethod;
return $this;
}
/**
* Post-process method signature. Provide *false* to skip.
*
* @param callable|false $processMethodSignature
*
* @return $this
*/
public function processMethodSignature($processMethodSignature)
{
$this->processMethodSignature = $processMethodSignature;
return $this;
}
/**
* Post-process method docblock contents. Provide *false* to skip.
*
* @param callable|false $processMethodDocBlock
*
* @return $this
*/
public function processMethodDocBlock($processMethodDocBlock)
{
$this->processMethodDocBlock = $processMethodDocBlock;
return $this;
}
/**
* Post-process property documentation. Provide *false* to skip.
*
* @param callable|false $processProperty
*
* @return $this
*/
public function processProperty($processProperty)
{
$this->processProperty = $processProperty;
return $this;
}
/**
* Post-process property signature. Provide *false* to skip.
*
* @param callable|false $processPropertySignature
*
* @return $this
*/
public function processPropertySignature($processPropertySignature)
{
$this->processPropertySignature = $processPropertySignature;
return $this;
}
/**
* Post-process property docblock contents. Provide *false* to skip.
*
* @param callable|false $processPropertyDocBlock
*
* @return $this
*/
public function processPropertyDocBlock($processPropertyDocBlock)
{
$this->processPropertyDocBlock = $processPropertyDocBlock;
return $this;
}
/**
* Use a function to reorder classes.
*
* @param callable $reorder
*
* @return $this
*/
public function reorder($reorder)
{
$this->reorder = $reorder;
return $this;
}
/**
* Use a function to reorder methods in class.
*
* @param callable $reorderMethods
*
* @return $this
*/
public function reorderMethods($reorderMethods)
{
$this->reorderMethods = $reorderMethods;
return $this;
}
/**
* @param callable $reorderProperties
*
* @return $this
*/
public function reorderProperties($reorderProperties)
{
$this->reorderProperties = $reorderProperties;
return $this;
}
/**
* @param string $filename
*
* @return $this
*/
public function filename($filename)
{
$this->filename = $filename;
return $this;
}
/**
* Inserts text at the beginning of markdown file.
*
* @param string $prepend
*
* @return $this
*/
public function prepend($prepend)
{
$this->prepend = $prepend;
return $this;
}
/**
* Inserts text at the end of markdown file.
*
* @param string $append
*
* @return $this
*/
public function append($append)
{
$this->append = $append;
return $this;
}
/**
* @param string $text
*
* @return $this
*/
public function text($text)
{
$this->text = $text;
return $this;
}
/**
* @param string $item
*
* @return $this
*/
public function textForClass($item)
{
$this->textForClass[] = $item;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
foreach ($this->docClass as $class) {
$this->printTaskInfo("Processing {class}", ['class' => $class]);
$this->textForClass[$class] = $this->documentClass($class);
}
if (is_callable($this->reorder)) {
$this->printTaskInfo("Applying reorder function");
call_user_func_array($this->reorder, [$this->textForClass]);
}
$this->text = implode("\n", $this->textForClass);
/** @var \Robo\Result $result */
$result = $this->collectionBuilder()->taskWriteToFile($this->filename)
->line($this->prepend)
->text($this->text)
->line($this->append)
->run();
$this->printTaskSuccess('{filename} created. {class-count} classes documented', ['filename' => $this->filename, 'class-count' => count($this->docClass)]);
return new Result($this, $result->getExitCode(), $result->getMessage(), $this->textForClass);
}
/**
* @param string $class
*
* @return null|string
*/
protected function documentClass($class)
{
if (!class_exists($class) && !trait_exists($class)) {
return "";
}
$refl = new \ReflectionClass($class);
if (is_callable($this->filterClasses)) {
$ret = call_user_func($this->filterClasses, $refl);
if (!$ret) {
return;
}
}
$doc = $this->documentClassSignature($refl);
$doc .= "\n" . $this->documentClassDocBlock($refl);
$doc .= "\n";
if (is_callable($this->processClass)) {
$doc = call_user_func($this->processClass, $refl, $doc);
}
$properties = [];
foreach ($refl->getProperties() as $reflProperty) {
$properties[] = $this->documentProperty($reflProperty);
}
$properties = array_filter($properties);
$doc .= implode("\n", $properties);
$methods = [];
foreach ($refl->getMethods() as $reflMethod) {
$methods[$reflMethod->name] = $this->documentMethod($reflMethod);
}
if (is_callable($this->reorderMethods)) {
call_user_func_array($this->reorderMethods, [&$methods]);
}
$methods = array_filter($methods);
$doc .= implode("\n", $methods) . "\n";
return $doc;
}
/**
* @param \ReflectionClass $reflectionClass
*
* @return string
*/
protected function documentClassSignature(\ReflectionClass $reflectionClass)
{
if ($this->processClassSignature === false) {
return "";
}
$signature = "## {$reflectionClass->name}\n\n";
if ($parent = $reflectionClass->getParentClass()) {
$signature .= "* *Extends* `{$parent->name}`";
}
$interfaces = $reflectionClass->getInterfaceNames();
if (count($interfaces)) {
$signature .= "\n* *Implements* `" . implode('`, `', $interfaces) . '`';
}
$traits = $reflectionClass->getTraitNames();
if (count($traits)) {
$signature .= "\n* *Uses* `" . implode('`, `', $traits) . '`';
}
if (is_callable($this->processClassSignature)) {
$signature = call_user_func($this->processClassSignature, $reflectionClass, $signature);
}
return $signature;
}
/**
* @param \ReflectionClass $reflectionClass
*
* @return string
*/
protected function documentClassDocBlock(\ReflectionClass $reflectionClass)
{
if ($this->processClassDocBlock === false) {
return "";
}
$doc = self::indentDoc($reflectionClass->getDocComment());
if (is_callable($this->processClassDocBlock)) {
$doc = call_user_func($this->processClassDocBlock, $reflectionClass, $doc);
}
return $doc;
}
/**
* @param \ReflectionMethod $reflectedMethod
*
* @return string
*/
protected function documentMethod(\ReflectionMethod $reflectedMethod)
{
if ($this->processMethod === false) {
return "";
}
if (is_callable($this->filterMethods)) {
$ret = call_user_func($this->filterMethods, $reflectedMethod);
if (!$ret) {
return "";
}
} else {
if (!$reflectedMethod->isPublic()) {
return "";
}
}
$signature = $this->documentMethodSignature($reflectedMethod);
$docblock = $this->documentMethodDocBlock($reflectedMethod);
$methodDoc = "$signature $docblock";
if (is_callable($this->processMethod)) {
$methodDoc = call_user_func($this->processMethod, $reflectedMethod, $methodDoc);
}
return $methodDoc;
}
/**
* @param \ReflectionProperty $reflectedProperty
*
* @return string
*/
protected function documentProperty(\ReflectionProperty $reflectedProperty)
{
if ($this->processProperty === false) {
return "";
}
if (is_callable($this->filterProperties)) {
$ret = call_user_func($this->filterProperties, $reflectedProperty);
if (!$ret) {
return "";
}
} else {
if (!$reflectedProperty->isPublic()) {
return "";
}
}
$signature = $this->documentPropertySignature($reflectedProperty);
$docblock = $this->documentPropertyDocBlock($reflectedProperty);
$propertyDoc = $signature . $docblock;
if (is_callable($this->processProperty)) {
$propertyDoc = call_user_func($this->processProperty, $reflectedProperty, $propertyDoc);
}
return $propertyDoc;
}
/**
* @param \ReflectionProperty $reflectedProperty
*
* @return string
*/
protected function documentPropertySignature(\ReflectionProperty $reflectedProperty)
{
if ($this->processPropertySignature === false) {
return "";
}
$modifiers = implode(' ', \Reflection::getModifierNames($reflectedProperty->getModifiers()));
$signature = "#### *$modifiers* {$reflectedProperty->name}";
if (is_callable($this->processPropertySignature)) {
$signature = call_user_func($this->processPropertySignature, $reflectedProperty, $signature);
}
return $signature;
}
/**
* @param \ReflectionProperty $reflectedProperty
*
* @return string
*/
protected function documentPropertyDocBlock(\ReflectionProperty $reflectedProperty)
{
if ($this->processPropertyDocBlock === false) {
return "";
}
$propertyDoc = $reflectedProperty->getDocComment();
// take from parent
if (!$propertyDoc) {
$parent = $reflectedProperty->getDeclaringClass();
while ($parent = $parent->getParentClass()) {
if ($parent->hasProperty($reflectedProperty->name)) {
$propertyDoc = $parent->getProperty($reflectedProperty->name)->getDocComment();
}
}
}
$propertyDoc = self::indentDoc($propertyDoc, 7);
$propertyDoc = preg_replace("~^@(.*?)([$\s])~", ' * `$1` $2', $propertyDoc); // format annotations
if (is_callable($this->processPropertyDocBlock)) {
$propertyDoc = call_user_func($this->processPropertyDocBlock, $reflectedProperty, $propertyDoc);
}
return ltrim($propertyDoc);
}
/**
* @param \ReflectionParameter $param
*
* @return string
*/
protected function documentParam(\ReflectionParameter $param)
{
$text = "";
if ($param->isArray()) {
$text .= 'array ';
}
if ($param->isCallable()) {
$text .= 'callable ';
}
$text .= '$' . $param->name;
if ($param->isDefaultValueAvailable()) {
if ($param->allowsNull()) {
$text .= ' = null';
} else {
$text .= ' = ' . str_replace("\n", ' ', print_r($param->getDefaultValue(), true));
}
}
return $text;
}
/**
* @param string $doc
* @param int $indent
*
* @return string
*/
public static function indentDoc($doc, $indent = 3)
{
if (!$doc) {
return $doc;
}
return implode(
"\n",
array_map(
function ($line) use ($indent) {
return substr($line, $indent);
},
explode("\n", $doc)
)
);
}
/**
* @param \ReflectionMethod $reflectedMethod
*
* @return string
*/
protected function documentMethodSignature(\ReflectionMethod $reflectedMethod)
{
if ($this->processMethodSignature === false) {
return "";
}
$modifiers = implode(' ', \Reflection::getModifierNames($reflectedMethod->getModifiers()));
$params = implode(
', ',
array_map(
function ($p) {
return $this->documentParam($p);
},
$reflectedMethod->getParameters()
)
);
$signature = "#### *$modifiers* {$reflectedMethod->name}($params)";
if (is_callable($this->processMethodSignature)) {
$signature = call_user_func($this->processMethodSignature, $reflectedMethod, $signature);
}
return $signature;
}
/**
* @param \ReflectionMethod $reflectedMethod
*
* @return string
*/
protected function documentMethodDocBlock(\ReflectionMethod $reflectedMethod)
{
if ($this->processMethodDocBlock === false) {
return "";
}
$methodDoc = $reflectedMethod->getDocComment();
// take from parent
if (!$methodDoc) {
$parent = $reflectedMethod->getDeclaringClass();
while ($parent = $parent->getParentClass()) {
if ($parent->hasMethod($reflectedMethod->name)) {
$methodDoc = $parent->getMethod($reflectedMethod->name)->getDocComment();
}
}
}
// take from interface
if (!$methodDoc) {
$interfaces = $reflectedMethod->getDeclaringClass()->getInterfaces();
foreach ($interfaces as $interface) {
$i = new \ReflectionClass($interface->name);
if ($i->hasMethod($reflectedMethod->name)) {
$methodDoc = $i->getMethod($reflectedMethod->name)->getDocComment();
break;
}
}
}
$methodDoc = self::indentDoc($methodDoc, 7);
$methodDoc = preg_replace("~^@(.*?) ([$\s])~m", ' * `$1` $2', $methodDoc); // format annotations
if (is_callable($this->processMethodDocBlock)) {
$methodDoc = call_user_func($this->processMethodDocBlock, $reflectedMethod, $methodDoc);
}
return $methodDoc;
}
}
<?php
namespace Robo\Task\Development;
use Robo\Result;
use Robo\Contract\TaskInterface;
use Robo\Exception\TaskException;
/**
* Helps to maintain `.semver` file.
*
* ```php
* <?php
* $this->taskSemVer('.semver')
* ->increment()
* ->run();
* ?>
* ```
*
*/
class SemVer implements TaskInterface
{
const SEMVER = "---\n:major: %d\n:minor: %d\n:patch: %d\n:special: '%s'\n:metadata: '%s'";
const REGEX = "/^\-\-\-\r?\n:major:\s(0|[1-9]\d*)\r?\n:minor:\s(0|[1-9]\d*)\r?\n:patch:\s(0|[1-9]\d*)\r?\n:special:\s'([a-zA-z0-9]*\.?(?:0|[1-9]\d*)?)'\r?\n:metadata:\s'((?:0|[1-9]\d*)?(?:\.[a-zA-z0-9\.]*)?)'/";
const REGEX_STRING = '/^(?<major>[0-9]+)\.(?<minor>[0-9]+)\.(?<patch>[0-9]+)(|-(?<special>[0-9a-zA-Z.]+))(|\+(?<metadata>[0-9a-zA-Z.]+))$/';
/**
* @var string
*/
protected $format = 'v%M.%m.%p%s';
/**
* @var string
*/
protected $specialSeparator = '-';
/**
* @var string
*/
protected $metadataSeparator = '+';
/**
* @var string
*/
protected $path;
/**
* @var array
*/
protected $version = [
'major' => 0,
'minor' => 0,
'patch' => 0,
'special' => '',
'metadata' => ''
];
/**
* @param string $filename
*/
public function __construct($filename = '')
{
$this->path = $filename;
if (file_exists($this->path)) {
$semverFileContents = file_get_contents($this->path);
$this->parseFile($semverFileContents);
}
}
/**
* @return string
*/
public function __toString()
{
$search = ['%M', '%m', '%p', '%s'];
$replace = $this->version + ['extra' => ''];
foreach (['special', 'metadata'] as $key) {
if (!empty($replace[$key])) {
$separator = $key . 'Separator';
$replace['extra'] .= $this->{$separator} . $replace[$key];
}
unset($replace[$key]);
}
return str_replace($search, $replace, $this->format);
}
/**
* @param string $version
*
* @return $this
*/
public function version($version)
{
$this->parseString($version);
return $this;
}
/**
* @param string $format
*
* @return $this
*/
public function setFormat($format)
{
$this->format = $format;
return $this;
}
/**
* @param string $separator
*
* @return $this
*/
public function setMetadataSeparator($separator)
{
$this->metadataSeparator = $separator;
return $this;
}
/**
* @param string $separator
*
* @return $this
*/
public function setPrereleaseSeparator($separator)
{
$this->specialSeparator = $separator;
return $this;
}
/**
* @param string $what
*
* @return $this
*
* @throws \Robo\Exception\TaskException
*/
public function increment($what = 'patch')
{
switch ($what) {
case 'major':
$this->version['major']++;
$this->version['minor'] = 0;
$this->version['patch'] = 0;
break;
case 'minor':
$this->version['minor']++;
$this->version['patch'] = 0;
break;
case 'patch':
$this->version['patch']++;
break;
default:
throw new TaskException(
$this,
'Bad argument, only one of the following is allowed: major, minor, patch'
);
}
return $this;
}
/**
* @param string $tag
*
* @return $this
*
* @throws \Robo\Exception\TaskException
*/
public function prerelease($tag = 'RC')
{
if (!is_string($tag)) {
throw new TaskException($this, 'Bad argument, only strings allowed.');
}
$number = 0;
if (!empty($this->version['special'])) {
list($current, $number) = explode('.', $this->version['special']);
if ($tag != $current) {
$number = 0;
}
}
$number++;
$this->version['special'] = implode('.', [$tag, $number]);
return $this;
}
/**
* @param array|string $data
*
* @return $this
*/
public function metadata($data)
{
if (is_array($data)) {
$data = implode('.', $data);
}
$this->version['metadata'] = $data;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$written = $this->dump();
return new Result($this, (int)($written === false), $this->__toString());
}
/**
* @return bool
*
* @throws \Robo\Exception\TaskException
*/
protected function dump()
{
if (empty($this->path)) {
return true;
}
$semver = sprintf(
self::SEMVER,
$this->version['major'],
$this->version['minor'],
$this->version['patch'],
$this->version['special'],
$this->version['metadata']
);
if (is_writeable($this->path) === false || file_put_contents($this->path, $semver) === false) {
throw new TaskException($this, 'Failed to write semver file.');
}
return true;
}
/**
* @param string $semverString
*
* @throws \Robo\Exception\TaskException
*/
protected function parseString($semverString)
{
if (!preg_match_all(self::REGEX_STRING, $semverString, $matches)) {
throw new TaskException($this, 'Bad semver value: ' . $semverString);
}
$this->version = array_intersect_key($matches, $this->version);
$this->version = array_map(function ($item) {
return $item[0];
}, $this->version);
}
/**
* @param string $semverFileContents
*
* @throws \Robo\Exception\TaskException
*/
protected function parseFile($semverFileContents)
{
if (!preg_match_all(self::REGEX, $semverFileContents, $matches)) {
throw new TaskException($this, 'Bad semver file.');
}
list(, $major, $minor, $patch, $special, $metadata) = array_map('current', $matches);
$this->version = compact('major', 'minor', 'patch', 'special', 'metadata');
}
}
<?php
namespace Robo\Task\Development;
use Robo\Task\BaseTask;
use Robo\Result;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Helps to manage changelog file.
* Creates or updates `changelog.md` file with recent changes in current version.
*
* ``` php
* <?php
* $version = "0.1.0";
* $this->taskChangelog()
* ->version($version)
* ->change("released to github")
* ->run();
* ?>
* ```
*
* Changes can be asked from Console
*
* ``` php
* <?php
* $this->taskChangelog()
* ->version($version)
* ->askForChanges()
* ->run();
* ?>
* ```
*/
class Changelog extends BaseTask implements BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var string
*/
protected $filename;
/**
* @var array
*/
protected $log = [];
/**
* @var string
*/
protected $anchor = "# Changelog";
/**
* @var string
*/
protected $version = "";
/**
* @var string
*/
protected $body = "";
/**
* @var string
*/
protected $header = "";
/**
* @param string $filename
*
* @return $this
*/
public function filename($filename)
{
$this->filename = $filename;
return $this;
}
/**
* Sets the changelog body text.
*
* This method permits the raw changelog text to be set directly If this is set, $this->log changes will be ignored.
*
* @param string $body
*
* @return $this
*/
public function setBody($body)
{
$this->body = $body;
return $this;
}
/**
* @param string $header
*
* @return $this
*/
public function setHeader($header)
{
$this->header = $header;
return $this;
}
/**
* @param string $item
*
* @return $this
*/
public function log($item)
{
$this->log[] = $item;
return $this;
}
/**
* @param string $anchor
*
* @return $this
*/
public function anchor($anchor)
{
$this->anchor = $anchor;
return $this;
}
/**
* @param string $version
*
* @return $this
*/
public function version($version)
{
$this->version = $version;
return $this;
}
/**
* @param string $filename
*/
public function __construct($filename)
{
$this->filename = $filename;
}
/**
* @param array $data
*
* @return $this
*/
public function changes(array $data)
{
$this->log = array_merge($this->log, $data);
return $this;
}
/**
* @param string $change
*
* @return $this
*/
public function change($change)
{
$this->log[] = $change;
return $this;
}
/**
* @return array
*/
public function getChanges()
{
return $this->log;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (empty($this->body)) {
if (empty($this->log)) {
return Result::error($this, "Changelog is empty");
}
$this->body = $this->generateBody();
}
if (empty($this->header)) {
$this->header = $this->generateHeader();
}
$text = $this->header . $this->body;
if (!file_exists($this->filename)) {
$this->printTaskInfo('Creating {filename}', ['filename' => $this->filename]);
$res = file_put_contents($this->filename, $this->anchor);
if ($res === false) {
return Result::error($this, "File {filename} cant be created", ['filename' => $this->filename]);
}
}
/** @var \Robo\Result $result */
// trying to append to changelog for today
$result = $this->collectionBuilder()->taskReplaceInFile($this->filename)
->from($this->header)
->to($text)
->run();
if (!isset($result['replaced']) || !$result['replaced']) {
$result = $this->collectionBuilder()->taskReplaceInFile($this->filename)
->from($this->anchor)
->to($this->anchor . "\n\n" . $text)
->run();
}
return new Result($this, $result->getExitCode(), $result->getMessage(), $this->log);
}
/**
* @return string
*/
protected function generateBody()
{
$text = implode("\n", array_map([$this, 'processLogRow'], $this->log));
$text .= "\n";
return $text;
}
/**
* @return string
*/
protected function generateHeader()
{
return "#### {$this->version}\n\n";
}
/**
* @param string $i
*
* @return string
*/
public function processLogRow($i)
{
return "* $i *" . date('Y-m-d') . "*";
}
}
<?php
namespace Robo\Task\Development;
use Robo\Task\BaseTask;
use Robo\Result;
/**
* Generate a Robo Task that is a wrapper around an existing class.
*
* ``` php
* <?php
* $this->taskGenerateTask('Symfony\Component\Filesystem\Filesystem', 'FilesystemStack')
* ->run();
* ```
*/
class GenerateTask extends BaseTask
{
/**
* @var string
*/
protected $className;
/**
* @var string
*/
protected $wrapperClassName;
/**
* @param string $className
* @param string $wrapperClassName
*/
public function __construct($className, $wrapperClassName = '')
{
$this->className = $className;
$this->wrapperClassName = $wrapperClassName;
}
/**
* {@inheritdoc}
*/
public function run()
{
$delegate = new \ReflectionClass($this->className);
$replacements = [];
$leadingCommentChars = " * ";
$methodDescriptions = [];
$methodImplementations = [];
$immediateMethods = [];
foreach ($delegate->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
$methodName = $method->name;
$getter = preg_match('/^(get|has|is)/', $methodName);
$setter = preg_match('/^(set|unset)/', $methodName);
$argPrototypeList = [];
$argNameList = [];
$needsImplementation = false;
foreach ($method->getParameters() as $arg) {
$argDescription = '$' . $arg->name;
$argNameList[] = $argDescription;
if ($arg->isOptional()) {
$argDescription = $argDescription . ' = ' . str_replace("\n", "", var_export($arg->getDefaultValue(), true));
// We will create wrapper methods for any method that
// has default parameters.
$needsImplementation = true;
}
$argPrototypeList[] = $argDescription;
}
$argPrototypeString = implode(', ', $argPrototypeList);
$argNameListString = implode(', ', $argNameList);
if ($methodName[0] != '_') {
$methodDescriptions[] = "@method $methodName($argPrototypeString)";
if ($getter) {
$immediateMethods[] = " public function $methodName($argPrototypeString)\n {\n return \$this->delegate->$methodName($argNameListString);\n }";
} elseif ($setter) {
$immediateMethods[] = " public function $methodName($argPrototypeString)\n {\n \$this->delegate->$methodName($argNameListString);\n return \$this;\n }";
} elseif ($needsImplementation) {
// Include an implementation for the wrapper method if necessary
$methodImplementations[] = " protected function _$methodName($argPrototypeString)\n {\n \$this->delegate->$methodName($argNameListString);\n }";
}
}
}
$classNameParts = explode('\\', $this->className);
$delegate = array_pop($classNameParts);
$delegateNamespace = implode('\\', $classNameParts);
if (empty($this->wrapperClassName)) {
$this->wrapperClassName = $delegate;
}
$replacements['{delegateNamespace}'] = $delegateNamespace;
$replacements['{delegate}'] = $delegate;
$replacements['{wrapperClassName}'] = $this->wrapperClassName;
$replacements['{taskname}'] = "task$delegate";
$replacements['{methodList}'] = $leadingCommentChars . implode("\n$leadingCommentChars", $methodDescriptions);
$replacements['{immediateMethods}'] = "\n\n" . implode("\n\n", $immediateMethods);
$replacements['{methodImplementations}'] = "\n\n" . implode("\n\n", $methodImplementations);
$template = file_get_contents(__DIR__ . '/../../../data/Task/Development/GeneratedWrapper.tmpl');
$template = str_replace(array_keys($replacements), array_values($replacements), $template);
// Returning data in the $message will cause it to be printed.
return Result::success($this, $template);
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Update
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerUpdate()->run();
*
* // prefer dist with custom path
* $this->taskComposerUpdate('path/to/my/composer.phar')
* ->preferDist()
* ->run();
*
* // optimize autoloader with custom path
* $this->taskComposerUpdate('path/to/my/composer.phar')
* ->optimizeAutoloader()
* ->run();
* ?>
* ```
*/
class Update extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'update';
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Updating Packages: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Init
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerInit()->run();
* ?>
* ```
*/
class Init extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'init';
/**
* @param string $projectName
*
* @return $this
*/
public function projectName($projectName)
{
$this->option('name', $projectName);
return $this;
}
/**
* @param string $description
*
* @return $this
*/
public function description($description)
{
$this->option('description', $description);
return $this;
}
/**
* @param string $author
*
* @return $this
*/
public function author($author)
{
$this->option('author', $author);
return $this;
}
/**
* @param string $type
*
* @return $this
*/
public function projectType($type)
{
$this->option('type', $type);
return $this;
}
/**
* @param string $homepage
*
* @return $this
*/
public function homepage($homepage)
{
$this->option('homepage', $homepage);
return $this;
}
/**
* 'require' is a keyword, so it cannot be a method name.
*
* @param string $project
* @param null|string $version
*
* @return $this
*/
public function dependency($project, $version = null)
{
if (isset($version)) {
$project .= ":$version";
}
$this->option('require', $project);
return $this;
}
/**
* @param string $stability
*
* @return $this
*/
public function stability($stability)
{
$this->option('stability', $stability);
return $this;
}
/**
* @param string $license
*
* @return $this
*/
public function license($license)
{
$this->option('license', $license);
return $this;
}
/**
* @param string $repository
*
* @return $this
*/
public function repository($repository)
{
$this->option('repository', $repository);
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Creating composer.json: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Install
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerInstall()->run();
*
* // prefer dist with custom path
* $this->taskComposerInstall('path/to/my/composer.phar')
* ->preferDist()
* ->run();
*
* // optimize autoloader with custom path
* $this->taskComposerInstall('path/to/my/composer.phar')
* ->optimizeAutoloader()
* ->run();
* ?>
* ```
*/
class Install extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'install';
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Installing Packages: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Require
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerRequire()->dependency('foo/bar', '^.2.4.8')->run();
* ?>
* ```
*/
class RequireDependency extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'require';
/**
* 'require' is a keyword, so it cannot be a method name.
*
* @param string $project
* @param null|string $version
*
* @return $this
*/
public function dependency($project, $version = null)
{
$project = (array)$project;
if (isset($version)) {
$project = array_map(
function ($item) use ($version) {
return "$item:$version";
},
$project
);
}
$this->args($project);
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Requiring packages: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Config
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerConfig()->set('bin-dir', 'bin/')->run();
* ?>
* ```
*/
class Config extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'config';
/**
* Set a configuration value.
*
* @param string $key
* @param string $value
*
* @return $this
*/
public function set($key, $value)
{
$this->arg($key);
$this->arg($value);
return $this;
}
/**
* Operate on the global repository
*
* @param bool $useGlobal
*
* @return $this
*/
public function useGlobal($useGlobal = true)
{
if ($useGlobal) {
$this->option('global');
}
return $this;
}
/**
* @param string $id
* @param string $uri
* @param string $repoType
*
* @return $this
*/
public function repository($id, $uri, $repoType = 'vcs')
{
$this->arg("repositories.$id");
$this->arg($repoType);
$this->arg($uri);
return $this;
}
/**
* @param string $id
*
* @return $this
*/
public function removeRepository($id)
{
$this->option('unset', "repositories.$id");
return $this;
}
/**
* @param string $id
*
* @return $this
*/
public function disableRepository($id)
{
$this->arg("repositories.$id");
$this->arg('false');
return $this;
}
/**
* @param string $id
*
* @return $this
*/
public function enableRepository($id)
{
$this->arg("repositories.$id");
$this->arg('true');
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Configuring composer.json: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer CreateProject
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerCreateProject()->source('foo/bar')->target('myBar')->run();
* ?>
* ```
*/
class CreateProject extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'create-project';
/**
* @var
*/
protected $source;
/**
* @var string
*/
protected $target = '';
/**
* @var string
*/
protected $version = '';
/**
* @param string $source
*
* @return $this
*/
public function source($source)
{
$this->source = $source;
return $this;
}
/**
* @param string $target
*
* @return $this
*/
public function target($target)
{
$this->target = $target;
return $this;
}
/**
* @param string $version
*
* @return $this
*/
public function version($version)
{
$this->version = $version;
return $this;
}
/**
* @param bool $keep
*
* @return $this
*/
public function keepVcs($keep = true)
{
if ($keep) {
$this->option('--keep-vcs');
}
return $this;
}
/**
* @param bool $noInstall
*
* @return $this
*/
public function noInstall($noInstall = true)
{
if ($noInstall) {
$this->option('--no-install');
}
return $this;
}
/**
* @param string $repository
*
* @return $this
*/
public function repository($repository)
{
if (!empty($repository)) {
$this->option('repository', $repository);
}
return $this;
}
/**
* @param string $stability
*
* @return $this
*/
public function stability($stability)
{
if (!empty($stability)) {
$this->option('stability', $stability);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function buildCommand()
{
$this->arg($this->source);
if (!empty($this->target)) {
$this->arg($this->target);
}
if (!empty($this->version)) {
$this->arg($this->version);
}
return parent::buildCommand();
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Creating project: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Dump Autoload
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerDumpAutoload()->run();
*
* // dump auto loader with custom path
* $this->taskComposerDumpAutoload('path/to/my/composer.phar')
* ->preferDist()
* ->run();
*
* // optimize autoloader dump with custom path
* $this->taskComposerDumpAutoload('path/to/my/composer.phar')
* ->optimize()
* ->run();
*
* // optimize autoloader dump with custom path and no dev
* $this->taskComposerDumpAutoload('path/to/my/composer.phar')
* ->optimize()
* ->noDev()
* ->run();
* ?>
* ```
*/
class DumpAutoload extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'dump-autoload';
/**
* @var string
*/
protected $optimize;
/**
* @param bool $optimize
*
* @return $this
*/
public function optimize($optimize = true)
{
if ($optimize) {
$this->option("--optimize");
}
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Dumping Autoloader: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Composer;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
abstract class Base extends BaseTask implements CommandInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command = '';
/**
* @var bool
*/
protected $built = false;
/**
* @var string
*/
protected $prefer;
/**
* @var string
*/
protected $dev;
/**
* @var string
*/
protected $ansi;
/**
* @var string
*/
protected $nointeraction;
/**
* Action to use
*
* @var string
*/
protected $action = '';
/**
* @param null|string $pathToComposer
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToComposer = null)
{
$this->command = $pathToComposer;
if (!$this->command) {
$this->command = $this->findExecutablePhar('composer');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "Neither local composer.phar nor global composer installation could be found.");
}
}
/**
* adds `prefer-dist` option to composer
*
* @param bool $preferDist
*
* @return $this
*/
public function preferDist($preferDist = true)
{
if (!$preferDist) {
return $this->preferSource();
}
$this->prefer = '--prefer-dist';
return $this;
}
/**
* adds `prefer-source` option to composer
*
* @return $this
*/
public function preferSource()
{
$this->prefer = '--prefer-source';
return $this;
}
/**
* adds `dev` option to composer
*
* @param bool $dev
*
* @return $this
*/
public function dev($dev = true)
{
if (!$dev) {
return $this->noDev();
}
$this->dev = '--dev';
return $this;
}
/**
* adds `no-dev` option to composer
*
* @return $this
*/
public function noDev()
{
$this->dev = '--no-dev';
return $this;
}
/**
* adds `ansi` option to composer
*
* @param bool $ansi
*
* @return $this
*/
public function ansi($ansi = true)
{
if (!$ansi) {
return $this->noAnsi();
}
$this->ansi = '--ansi';
return $this;
}
/**
* adds `no-ansi` option to composer
*
* @return $this
*/
public function noAnsi()
{
$this->ansi = '--no-ansi';
return $this;
}
/**
* @param bool $interaction
*
* @return $this
*/
public function interaction($interaction = true)
{
if (!$interaction) {
return $this->noInteraction();
}
return $this;
}
/**
* adds `no-interaction` option to composer
*
* @return $this
*/
public function noInteraction()
{
$this->nointeraction = '--no-interaction';
return $this;
}
/**
* adds `optimize-autoloader` option to composer
*
* @param bool $optimize
*
* @return $this
*/
public function optimizeAutoloader($optimize = true)
{
if ($optimize) {
$this->option('--optimize-autoloader');
}
return $this;
}
/**
* adds `ignore-platform-reqs` option to composer
*
* @param bool $ignore
*
* @return $this
*/
public function ignorePlatformRequirements($ignore = true)
{
$this->option('--ignore-platform-reqs');
return $this;
}
/**
* disable plugins
*
* @param bool $disable
*
* @return $this
*/
public function disablePlugins($disable = true)
{
if ($disable) {
$this->option('--no-plugins');
}
return $this;
}
/**
* skip scripts
*
* @param bool $disable
*
* @return $this
*/
public function noScripts($disable = true)
{
if ($disable) {
$this->option('--no-scripts');
}
return $this;
}
/**
* adds `--working-dir $dir` option to composer
*
* @param string $dir
*
* @return $this
*/
public function workingDir($dir)
{
$this->option("--working-dir", $dir);
return $this;
}
/**
* Copy class fields into command options as directed.
*/
public function buildCommand()
{
if (!isset($this->ansi) && $this->getConfig()->get(\Robo\Config\Config::DECORATED)) {
$this->ansi();
}
if (!isset($this->nointeraction) && !$this->getConfig()->get(\Robo\Config\Config::INTERACTIVE)) {
$this->noInteraction();
}
$this->option($this->prefer)
->option($this->dev)
->option($this->nointeraction)
->option($this->ansi);
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
if (!$this->built) {
$this->buildCommand();
$this->built = true;
}
return "{$this->command} {$this->action}{$this->arguments}";
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Remove
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerRemove()->run();
* ?>
* ```
*/
class Remove extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'remove';
/**
* @param bool $dev
*
* @return $this
*/
public function dev($dev = true)
{
if ($dev) {
$this->option('--dev');
}
return $this;
}
/**
* @param bool $noProgress
*
* @return $this
*/
public function noProgress($noProgress = true)
{
if ($noProgress) {
$this->option('--no-progress');
}
return $this;
}
/**
* @param bool $noUpdate
*
* @return $this
*/
public function noUpdate($noUpdate = true)
{
if ($noUpdate) {
$this->option('--no-update');
}
return $this;
}
/**
* @param bool $updateNoDev
*
* @return $this
*/
public function updateNoDev($updateNoDev = true)
{
if ($updateNoDev) {
$this->option('--update-no-dev');
}
return $this;
}
/**
* @param bool $updateWithDependencies
*
* @return $this
*/
public function noUpdateWithDependencies($updateWithDependencies = true)
{
if ($updateWithDependencies) {
$this->option('--no-update-with-dependencies');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Removing packages: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Composer;
trait loadTasks
{
/**
* @param null|string $pathToComposer
*
* @return \Robo\Task\Composer\Install|\Robo\Collection\CollectionBuilder
*/
protected function taskComposerInstall($pathToComposer = null)
{
return $this->task(Install::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return \Robo\Task\Composer\Update|\Robo\Collection\CollectionBuilder
*/
protected function taskComposerUpdate($pathToComposer = null)
{
return $this->task(Update::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return \Robo\Task\Composer\DumpAutoload|\Robo\Collection\CollectionBuilder
*/
protected function taskComposerDumpAutoload($pathToComposer = null)
{
return $this->task(DumpAutoload::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return \Robo\Task\Composer\Init|\Robo\Collection\CollectionBuilder
*/
protected function taskComposerInit($pathToComposer = null)
{
return $this->task(Init::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return \Robo\Task\Composer\Config|\Robo\Collection\CollectionBuilder
*/
protected function taskComposerConfig($pathToComposer = null)
{
return $this->task(Config::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return \Robo\Task\Composer\Validate|\Robo\Collection\CollectionBuilder
*/
protected function taskComposerValidate($pathToComposer = null)
{
return $this->task(Validate::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return \Robo\Task\Composer\Remove|\Robo\Collection\CollectionBuilder
*/
protected function taskComposerRemove($pathToComposer = null)
{
return $this->task(Remove::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return \Robo\Task\Composer\RequireDependency|\Robo\Collection\CollectionBuilder
*/
protected function taskComposerRequire($pathToComposer = null)
{
return $this->task(RequireDependency::class, $pathToComposer);
}
/**
* @param null|string $pathToComposer
*
* @return \Robo\Task\Composer\CreateProject|\Robo\Collection\CollectionBuilder
*/
protected function taskComposerCreateProject($pathToComposer = null)
{
return $this->task(CreateProject::class, $pathToComposer);
}
}
<?php
namespace Robo\Task\Composer;
/**
* Composer Validate
*
* ``` php
* <?php
* // simple execution
* $this->taskComposerValidate()->run();
* ?>
* ```
*/
class Validate extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'validate';
/**
* @param bool $noCheckAll
*
* @return $this
*/
public function noCheckAll($noCheckAll = true)
{
if ($noCheckAll) {
$this->option('--no-check-all');
}
return $this;
}
/**
* @param bool $noCheckLock
*
* @return $this
*/
public function noCheckLock($noCheckLock = true)
{
if ($noCheckLock) {
$this->option('--no-check-lock');
}
return $this;
}
/**
* @param bool $noCheckPublish
*
* @return $this
*/
public function noCheckPublish($noCheckPublish = true)
{
if ($noCheckPublish) {
$this->option('--no-check-publish');
}
return $this;
}
/**
* @param bool $withDependencies
*
* @return $this
*/
public function withDependencies($withDependencies = true)
{
if ($withDependencies) {
$this->option('--with-dependencies');
}
return $this;
}
/**
* @param bool $strict
*
* @return $this
*/
public function strict($strict = true)
{
if ($strict) {
$this->option('--strict');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Validating composer.json: {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Result;
/**
* Mirrors a directory to another
*
* ``` php
* <?php
* $this->taskMirrorDir(['dist/config/' => 'config/'])->run();
* // or use shortcut
* $this->_mirrorDir('dist/config/', 'config/');
*
* ?>
* ```
*/
class MirrorDir extends BaseDir
{
/**
* {@inheritdoc}
*/
public function run()
{
foreach ($this->dirs as $src => $dst) {
$this->fs->mirror(
$src,
$dst,
null,
[
'override' => true,
'copy_on_windows' => true,
'delete' => true
]
);
$this->printTaskInfo("Mirrored from {source} to {destination}", ['source' => $src, 'destination' => $dst]);
}
return Result::success($this);
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Result;
use Robo\Exception\TaskException;
use Symfony\Component\Finder\Finder;
/**
* Searches for files in a nested directory structure and copies them to
* a target directory with or without the parent directories. The task was
* inspired by [gulp-flatten](https://www.npmjs.com/package/gulp-flatten).
*
* Example directory structure:
*
* ```
* └── assets
* ├── asset-library1
* │ ├── README.md
* │ └── asset-library1.min.js
* └── asset-library2
* ├── README.md
* └── asset-library2.min.js
* ```
*
* The following code will search the `*.min.js` files and copy them
* inside a new `dist` folder:
*
* ``` php
* <?php
* $this->taskFlattenDir(['assets/*.min.js' => 'dist'])->run();
* // or use shortcut
* $this->_flattenDir('assets/*.min.js', 'dist');
* ?>
* ```
*
* You can also define the target directory with an additional method, instead of
* key/value pairs. More similar to the gulp-flatten syntax:
*
* ``` php
* <?php
* $this->taskFlattenDir(['assets/*.min.js'])
* ->to('dist')
* ->run();
* ?>
* ```
*
* You can also append parts of the parent directories to the target path. If you give
* the value `1` to the `includeParents()` method, then the top parent will be appended
* to the target directory resulting in a path such as `dist/assets/asset-library1.min.js`.
*
* If you give a negative number, such as `-1` (the same as specifying `array(0, 1)` then
* the bottom parent will be appended, resulting in a path such as
* `dist/asset-library1/asset-library1.min.js`.
*
* The top parent directory will always be starting from the relative path to the current
* directory. You can override that with the `parentDir()` method. If in the above example
* you would specify `assets`, then the top parent directory would be `asset-library1`.
*
* ``` php
* <?php
* $this->taskFlattenDir(['assets/*.min.js' => 'dist'])
* ->parentDir('assets')
* ->includeParents(1)
* ->run();
* ?>
* ```
*/
class FlattenDir extends BaseDir
{
/**
* @var int
*/
protected $chmod = 0755;
/**
* @var int[]
*/
protected $parents = array(0, 0);
/**
* @var string
*/
protected $parentDir = '';
/**
* @var string
*/
protected $to;
/**
* {@inheritdoc}
*/
public function __construct($dirs)
{
parent::__construct($dirs);
$this->parentDir = getcwd();
}
/**
* {@inheritdoc}
*/
public function run()
{
// find the files
$files = $this->findFiles($this->dirs);
// copy the files
$this->copyFiles($files);
$fileNoun = count($files) == 1 ? ' file' : ' files';
$this->printTaskSuccess("Copied {count} $fileNoun to {destination}", ['count' => count($files), 'destination' => $this->to]);
return Result::success($this);
}
/**
* Sets the default folder permissions for the destination if it does not exist.
*
* @link http://en.wikipedia.org/wiki/Chmod
* @link http://php.net/manual/en/function.mkdir.php
* @link http://php.net/manual/en/function.chmod.php
*
* @param int $permission
*
* @return $this
*/
public function dirPermissions($permission)
{
$this->chmod = (int) $permission;
return $this;
}
/**
* Sets the value from which direction and how much parent dirs should be included.
* Accepts a positive or negative integer or an array with two integer values.
*
* @param int|int[] $parents
*
* @return $this
*
* @throws TaskException
*/
public function includeParents($parents)
{
if (is_int($parents)) {
// if an integer is given check whether it is for top or bottom parent
if ($parents >= 0) {
$this->parents[0] = $parents;
return $this;
}
$this->parents[1] = 0 - $parents;
return $this;
}
if (is_array($parents)) {
// check if the array has two values no more, no less
if (count($parents) == 2) {
$this->parents = $parents;
return $this;
}
}
throw new TaskException($this, 'includeParents expects an integer or an array with two values');
}
/**
* Sets the parent directory from which the relative parent directories will be calculated.
*
* @param string $dir
*
* @return $this
*/
public function parentDir($dir)
{
if (!$this->fs->isAbsolutePath($dir)) {
// attach the relative path to current working directory
$dir = getcwd() . '/' . $dir;
}
$this->parentDir = $dir;
return $this;
}
/**
* Sets the target directory where the files will be copied to.
*
* @param string $target
*
* @return $this
*/
public function to($target)
{
$this->to = rtrim($target, '/');
return $this;
}
/**
* @param array $dirs
*
* @return array|\Robo\Result
*
* @throws \Robo\Exception\TaskException
*/
protected function findFiles($dirs)
{
$files = array();
// find the files
foreach ($dirs as $k => $v) {
// reset finder
$finder = new Finder();
$dir = $k;
$to = $v;
// check if target was given with the to() method instead of key/value pairs
if (is_int($k)) {
$dir = $v;
if (isset($this->to)) {
$to = $this->to;
} else {
throw new TaskException($this, 'target directory is not defined');
}
}
try {
$finder->files()->in($dir);
} catch (\InvalidArgumentException $e) {
// if finder cannot handle it, try with in()->name()
if (strpos($dir, '/') === false) {
$dir = './' . $dir;
}
$parts = explode('/', $dir);
$new_dir = implode('/', array_slice($parts, 0, -1));
try {
$finder->files()->in($new_dir)->name(array_pop($parts));
} catch (\InvalidArgumentException $e) {
return Result::fromException($this, $e);
}
}
foreach ($finder as $file) {
// store the absolute path as key and target as value in the files array
$files[$file->getRealpath()] = $this->getTarget($file->getRealPath(), $to);
}
$fileNoun = count($files) == 1 ? ' file' : ' files';
$this->printTaskInfo("Found {count} $fileNoun in {dir}", ['count' => count($files), 'dir' => $dir]);
}
return $files;
}
/**
* @param string $file
* @param string $to
*
* @return string
*/
protected function getTarget($file, $to)
{
$target = $to . '/' . basename($file);
if ($this->parents !== array(0, 0)) {
// if the parent is set, create additional directories inside target
// get relative path to parentDir
$rel_path = $this->fs->makePathRelative(dirname($file), $this->parentDir);
// get top parents and bottom parents
$parts = explode('/', rtrim($rel_path, '/'));
$prefix_dir = '';
$prefix_dir .= ($this->parents[0] > 0 ? implode('/', array_slice($parts, 0, $this->parents[0])) . '/' : '');
$prefix_dir .= ($this->parents[1] > 0 ? implode('/', array_slice($parts, (0 - $this->parents[1]), $this->parents[1])) : '');
$prefix_dir = rtrim($prefix_dir, '/');
$target = $to . '/' . $prefix_dir . '/' . basename($file);
}
return $target;
}
/**
* @param array $files
*/
protected function copyFiles($files)
{
// copy the files
foreach ($files as $from => $to) {
// check if target dir exists
if (!is_dir(dirname($to))) {
$this->fs->mkdir(dirname($to), $this->chmod);
}
$this->fs->copy($from, $to);
}
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Task\BaseTask;
use Symfony\Component\Filesystem\Filesystem as sfFilesystem;
abstract class BaseDir extends BaseTask
{
/**
* @var string[]
*/
protected $dirs = [];
/**
* @var \Symfony\Component\Filesystem\Filesystem
*/
protected $fs;
/**
* @param string|string[] $dirs
*/
public function __construct($dirs)
{
is_array($dirs)
? $this->dirs = $dirs
: $this->dirs[] = $dirs;
$this->fs = new sfFilesystem();
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Common\ResourceExistenceChecker;
use Robo\Result;
/**
* Deletes dir
*
* ``` php
* <?php
* $this->taskDeleteDir('tmp')->run();
* // as shortcut
* $this->_deleteDir(['tmp', 'log']);
* ?>
* ```
*/
class DeleteDir extends BaseDir
{
use ResourceExistenceChecker;
/**
* {@inheritdoc}
*/
public function run()
{
if (!$this->checkResources($this->dirs, 'dir')) {
return Result::error($this, 'Source directories are missing!');
}
foreach ($this->dirs as $dir) {
$this->fs->remove($dir);
$this->printTaskInfo("Deleted {dir}...", ['dir' => $dir]);
}
return Result::success($this);
}
}
<?php
namespace Robo\Task\Filesystem;
trait loadShortcuts
{
/**
* @param string $src
* @param string $dst
*
* @return \Robo\Result
*/
protected function _copyDir($src, $dst)
{
return $this->taskCopyDir([$src => $dst])->run();
}
/**
* @param string $src
* @param string $dst
*
* @return \Robo\Result
*/
protected function _mirrorDir($src, $dst)
{
return $this->taskMirrorDir([$src => $dst])->run();
}
/**
* @param string|string[] $dir
*
* @return \Robo\Result
*/
protected function _deleteDir($dir)
{
return $this->taskDeleteDir($dir)->run();
}
/**
* @param string|string[] $dir
*
* @return \Robo\Result
*/
protected function _cleanDir($dir)
{
return $this->taskCleanDir($dir)->run();
}
/**
* @param string $from
* @param string $to
* @param bool $overwrite
*
* @return \Robo\Result
*/
protected function _rename($from, $to, $overwrite = false)
{
return $this->taskFilesystemStack()->rename($from, $to, $overwrite)->run();
}
/**
* @param string|string[] $dir
*
* @return \Robo\Result
*/
protected function _mkdir($dir)
{
return $this->taskFilesystemStack()->mkdir($dir)->run();
}
/**
* @param string $prefix
* @param string $base
* @param bool $includeRandomPart
*
* @return string
*/
protected function _tmpDir($prefix = 'tmp', $base = '', $includeRandomPart = true)
{
$result = $this->taskTmpDir($prefix, $base, $includeRandomPart)->run();
return isset($result['path']) ? $result['path'] : '';
}
/**
* @param string $file
*
* @return \Robo\Result
*/
protected function _touch($file)
{
return $this->taskFilesystemStack()->touch($file)->run();
}
/**
* @param string|string[] $file
*
* @return \Robo\Result
*/
protected function _remove($file)
{
return $this->taskFilesystemStack()->remove($file)->run();
}
/**
* @param string|string[] $file
* @param string $group
*
* @return \Robo\Result
*/
protected function _chgrp($file, $group)
{
return $this->taskFilesystemStack()->chgrp($file, $group)->run();
}
/**
* @param string|string[] $file
* @param int $permissions
* @param int $umask
* @param bool $recursive
*
* @return \Robo\Result
*/
protected function _chmod($file, $permissions, $umask = 0000, $recursive = false)
{
return $this->taskFilesystemStack()->chmod($file, $permissions, $umask, $recursive)->run();
}
/**
* @param string $from
* @param string $to
*
* @return \Robo\Result
*/
protected function _symlink($from, $to)
{
return $this->taskFilesystemStack()->symlink($from, $to)->run();
}
/**
* @param string $from
* @param string $to
*
* @return \Robo\Result
*/
protected function _copy($from, $to)
{
return $this->taskFilesystemStack()->copy($from, $to)->run();
}
/**
* @param string $from
* @param string $to
*
* @return \Robo\Result
*/
protected function _flattenDir($from, $to)
{
return $this->taskFlattenDir([$from => $to])->run();
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Common\ResourceExistenceChecker;
use Robo\Result;
/**
* Deletes all files from specified dir, ignoring git files.
*
* ``` php
* <?php
* $this->taskCleanDir(['tmp','logs'])->run();
* // as shortcut
* $this->_cleanDir('app/cache');
* ?>
* ```
*/
class CleanDir extends BaseDir
{
use ResourceExistenceChecker;
/**
* {@inheritdoc}
*/
public function run()
{
if (!$this->checkResources($this->dirs, 'dir')) {
return Result::error($this, 'Source directories are missing!');
}
foreach ($this->dirs as $dir) {
$this->emptyDir($dir);
$this->printTaskInfo("Cleaned {dir}", ['dir' => $dir]);
}
return Result::success($this);
}
/**
* @param string $path
*/
protected function emptyDir($path)
{
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($path),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $path) {
if ($path->isDir()) {
$dir = (string)$path;
if (basename($dir) === '.' || basename($dir) === '..') {
continue;
}
$this->fs->remove($dir);
} else {
$file = (string)$path;
if (basename($file) === '.gitignore' || basename($file) === '.gitkeep') {
continue;
}
$this->fs->remove($file);
}
}
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Common\ResourceExistenceChecker;
use Robo\Result;
use Robo\Exception\TaskException;
/**
* Copies one dir into another
*
* ``` php
* <?php
* $this->taskCopyDir(['dist/config' => 'config'])->run();
* // as shortcut
* $this->_copyDir('dist/config', 'config');
* ?>
* ```
*/
class CopyDir extends BaseDir
{
use ResourceExistenceChecker;
/**
* Explicitly declare our consturctor, so that
* our copyDir() method does not look like a php4 constructor.
*
* @param string|string[] $dirs
*/
public function __construct($dirs)
{
parent::__construct($dirs);
}
/**
* @var int
*/
protected $chmod = 0755;
/**
* Files to exclude on copying.
*
* @var string[]
*/
protected $exclude = [];
/**
* Overwrite destination files newer than source files.
*/
protected $overwrite = true;
/**
* {@inheritdoc}
*/
public function run()
{
if (!$this->checkResources($this->dirs, 'dir')) {
return Result::error($this, 'Source directories are missing!');
}
foreach ($this->dirs as $src => $dst) {
$this->copyDir($src, $dst);
$this->printTaskInfo('Copied from {source} to {destination}', ['source' => $src, 'destination' => $dst]);
}
return Result::success($this);
}
/**
* Sets the default folder permissions for the destination if it doesn't exist
*
* @link http://en.wikipedia.org/wiki/Chmod
* @link http://php.net/manual/en/function.mkdir.php
* @link http://php.net/manual/en/function.chmod.php
*
* @param int $value
*
* @return $this
*/
public function dirPermissions($value)
{
$this->chmod = (int)$value;
return $this;
}
/**
* List files to exclude.
*
* @param string[] $exclude
*
* @return $this
*/
public function exclude($exclude = [])
{
$this->exclude = $this->simplifyForCompare($exclude);
return $this;
}
/**
* Destination files newer than source files are overwritten.
*
* @param bool $overwrite
*
* @return $this
*/
public function overwrite($overwrite)
{
$this->overwrite = $overwrite;
return $this;
}
/**
* Copies a directory to another location.
*
* @param string $src Source directory
* @param string $dst Destination directory
* @param string $parent Parent directory
*
* @throws \Robo\Exception\TaskException
*/
protected function copyDir($src, $dst, $parent = '')
{
$dir = @opendir($src);
if (false === $dir) {
throw new TaskException($this, "Cannot open source directory '" . $src . "'");
}
if (!is_dir($dst)) {
mkdir($dst, $this->chmod, true);
}
while (false !== ($file = readdir($dir))) {
// Support basename and full path exclusion.
if ($this->excluded($file, $src, $parent)) {
continue;
}
$srcFile = $src . '/' . $file;
$destFile = $dst . '/' . $file;
if (is_dir($srcFile)) {
$this->copyDir($srcFile, $destFile, $parent . $file . DIRECTORY_SEPARATOR);
} else {
$this->fs->copy($srcFile, $destFile, $this->overwrite);
}
}
closedir($dir);
}
/**
* Check to see if the current item is excluded.
*
* @param string $file
* @param string $src
* @param string $parent
*
* @return bool
*/
protected function excluded($file, $src, $parent)
{
return
($file == '.') ||
($file == '..') ||
in_array($file, $this->exclude) ||
in_array($this->simplifyForCompare($parent . $file), $this->exclude) ||
in_array($this->simplifyForCompare($src . DIRECTORY_SEPARATOR . $file), $this->exclude);
}
/**
* Avoid problems comparing paths on Windows that may have a
* combination of DIRECTORY_SEPARATOR and /.
*
* @param string$item
*
* @return string
*/
protected function simplifyForCompare($item)
{
return str_replace(DIRECTORY_SEPARATOR, '/', $item);
}
}
<?php
namespace Robo\Task\Filesystem;
trait loadTasks
{
/**
* @param string|string[] $dirs
*
* @return \Robo\Task\Filesystem\CleanDir|\Robo\Collection\CollectionBuilder
*/
protected function taskCleanDir($dirs)
{
return $this->task(CleanDir::class, $dirs);
}
/**
* @param string|string[] $dirs
*
* @return \Robo\Task\Filesystem\DeleteDir|\Robo\Collection\CollectionBuilder
*/
protected function taskDeleteDir($dirs)
{
return $this->task(DeleteDir::class, $dirs);
}
/**
* @param string $prefix
* @param string $base
* @param bool $includeRandomPart
*
* @return \Robo\Task\Filesystem\WorkDir|\Robo\Collection\CollectionBuilder
*/
protected function taskTmpDir($prefix = 'tmp', $base = '', $includeRandomPart = true)
{
return $this->task(TmpDir::class, $prefix, $base, $includeRandomPart);
}
/**
* @param string $finalDestination
*
* @return \Robo\Task\Filesystem\TmpDir|\Robo\Collection\CollectionBuilder
*/
protected function taskWorkDir($finalDestination)
{
return $this->task(WorkDir::class, $finalDestination);
}
/**
* @param string|string[] $dirs
*
* @return \Robo\Task\Filesystem\CopyDir|\Robo\Collection\CollectionBuilder
*/
protected function taskCopyDir($dirs)
{
return $this->task(CopyDir::class, $dirs);
}
/**
* @param string|string[] $dirs
*
* @return \Robo\Task\Filesystem\MirrorDir|\Robo\Collection\CollectionBuilder
*/
protected function taskMirrorDir($dirs)
{
return $this->task(MirrorDir::class, $dirs);
}
/**
* @param string|string[] $dirs
*
* @return \Robo\Task\Filesystem\FlattenDir|\Robo\Collection\CollectionBuilder
*/
protected function taskFlattenDir($dirs)
{
return $this->task(FlattenDir::class, $dirs);
}
/**
* @return \Robo\Task\Filesystem\FilesystemStack|\Robo\Collection\CollectionBuilder
*/
protected function taskFilesystemStack()
{
return $this->task(FilesystemStack::class);
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Result;
use Robo\Contract\CompletionInterface;
/**
* Create a temporary directory that is automatically cleaned up
* once the task collection is is part of completes.
*
* Use WorkDir if you do not want the directory to be deleted.
*
* ``` php
* <?php
* // Delete on rollback or on successful completion.
* // Note that in this example, everything is deleted at
* // the end of $collection->run().
* $collection = $this->collectionBuilder();
* $tmpPath = $collection->tmpDir()->getPath();
* $collection->taskFilesystemStack()
* ->mkdir("$tmpPath/log")
* ->touch("$tmpPath/log/error.txt");
* $collection->run();
* // as shortcut (deleted when program exits)
* $tmpPath = $this->_tmpDir();
* ?>
* ```
*/
class TmpDir extends BaseDir implements CompletionInterface
{
/**
* @var string
*/
protected $base;
/**
* @var string
*/
protected $prefix;
/**
* @var bool
*/
protected $cwd;
/**
* @var string
*/
protected $savedWorkingDirectory;
/**
* @param string $prefix
* @param string $base
* @param bool $includeRandomPart
*/
public function __construct($prefix = 'tmp', $base = '', $includeRandomPart = true)
{
if (empty($base)) {
$base = sys_get_temp_dir();
}
$path = "{$base}/{$prefix}";
if ($includeRandomPart) {
$path = static::randomLocation($path);
}
parent::__construct(["$path"]);
}
/**
* Add a random part to a path, ensuring that the directory does
* not (currently) exist.
*
* @param string $path The base/prefix path to add a random component to
* @param int $length Number of digits in the random part
*
* @return string
*/
protected static function randomLocation($path, $length = 12)
{
$random = static::randomString($length);
while (is_dir("{$path}_{$random}")) {
$random = static::randomString($length);
}
return "{$path}_{$random}";
}
/**
* Generate a suitably random string to use as the suffix for our
* temporary directory.
*
* @param int $length
*
* @return string
*/
protected static function randomString($length = 12)
{
return substr(str_shuffle('23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'), 0, max($length, 3));
}
/**
* Flag that we should cwd to the temporary directory when it is
* created, and restore the old working directory when it is deleted.
*
* @param bool $shouldChangeWorkingDirectory
*
* @return $this
*/
public function cwd($shouldChangeWorkingDirectory = true)
{
$this->cwd = $shouldChangeWorkingDirectory;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
// Save the current working directory
$this->savedWorkingDirectory = getcwd();
foreach ($this->dirs as $dir) {
$this->fs->mkdir($dir);
$this->printTaskInfo("Created {dir}...", ['dir' => $dir]);
// Change the current working directory, if requested
if ($this->cwd) {
chdir($dir);
}
}
return Result::success($this, '', ['path' => $this->getPath()]);
}
protected function restoreWorkingDirectory()
{
// Restore the current working directory, if we redirected it.
if ($this->cwd) {
chdir($this->savedWorkingDirectory);
}
}
protected function deleteTmpDir()
{
foreach ($this->dirs as $dir) {
$this->fs->remove($dir);
}
}
/**
* Delete this directory when our collection completes.
* If this temporary directory is not part of a collection,
* then it will be deleted when the program terminates,
* presuming that it was created by taskTmpDir() or _tmpDir().
*/
public function complete()
{
$this->restoreWorkingDirectory();
$this->deleteTmpDir();
}
/**
* Get a reference to the path to the temporary directory, so that
* it may be used to create other tasks. Note that the directory
* is not actually created until the task runs.
*
* @return string
*/
public function getPath()
{
return $this->dirs[0];
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Result;
use Robo\Contract\RollbackInterface;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Create a temporary working directory that is automatically renamed to its
* final desired location if all of the tasks in the collection succeed. If
* there is a rollback, then the working directory is deleted.
*
* ``` php
* <?php
* $collection = $this->collectionBuilder();
* $workingPath = $collection->workDir("build")->getPath();
* $collection->taskFilesystemStack()
* ->mkdir("$workingPath/log")
* ->touch("$workingPath/log/error.txt");
* $collection->run();
* ?>
* ```
*/
class WorkDir extends TmpDir implements RollbackInterface, BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var string
*/
protected $finalDestination;
/**
* @param string $finalDestination
*/
public function __construct($finalDestination)
{
$this->finalDestination = $finalDestination;
// Create a temporary directory to work in. We will place our
// temporary directory in the same location as the final destination
// directory, so that the work directory can be moved into place
// without having to be copied, e.g. in a cross-volume rename scenario.
parent::__construct(basename($finalDestination), dirname($finalDestination));
}
/**
* Create our working directory.
*
* @return \Robo\Result
*/
public function run()
{
// Destination cannot be empty
if (empty($this->finalDestination)) {
return Result::error($this, "Destination directory not specified.");
}
// Before we do anything else, ensure that any directory in the
// final destination is writable, so that we can at a minimum
// move it out of the way before placing our results there.
if (is_dir($this->finalDestination)) {
if (!is_writable($this->finalDestination)) {
return Result::error($this, "Destination directory {dir} exists and cannot be overwritten.", ['dir' => $this->finalDestination]);
}
}
return parent::run();
}
/**
* Move our working directory into its final destination once the
* collection it belongs to completes.
*/
public function complete()
{
$this->restoreWorkingDirectory();
// Delete the final destination, if it exists.
// Move it out of the way first, in case it cannot
// be completely deleted.
if (file_exists($this->finalDestination)) {
$temporaryLocation = static::randomLocation($this->finalDestination . '_TO_DELETE_');
// This should always work, because we already created a temporary
// folder in the parent directory of the final destination, and we
// have already checked to confirm that the final destination is
// writable.
rename($this->finalDestination, $temporaryLocation);
// This may silently fail, leaving artifacts behind, if there
// are permissions problems with some items somewhere inside
// the folder being deleted.
$this->fs->remove($temporaryLocation);
}
// Move our working directory over the final destination.
// This should never be a cross-volume rename, so this should
// always succeed.
$workDir = reset($this->dirs);
if (file_exists($workDir)) {
rename($workDir, $this->finalDestination);
}
}
/**
* Delete our working directory
*/
public function rollback()
{
$this->restoreWorkingDirectory();
$this->deleteTmpDir();
}
/**
* Get a reference to the path to the temporary directory, so that
* it may be used to create other tasks. Note that the directory
* is not actually created until the task runs.
*
* @return string
*/
public function getPath()
{
return $this->dirs[0];
}
}
<?php
namespace Robo\Task\Filesystem;
use Robo\Task\StackBasedTask;
use Symfony\Component\Filesystem\Filesystem as sfFilesystem;
use Symfony\Component\Filesystem\Exception\IOException;
use Robo\Contract\BuilderAwareInterface;
use Robo\Common\BuilderAwareTrait;
/**
* Wrapper for [Symfony Filesystem](http://symfony.com/doc/current/components/filesystem.html) Component.
* Comands are executed in stack and can be stopped on first fail with `stopOnFail` option.
*
* ``` php
* <?php
* $this->taskFilesystemStack()
* ->mkdir('logs')
* ->touch('logs/.gitignore')
* ->chgrp('www', 'www-data')
* ->symlink('/var/log/nginx/error.log', 'logs/error.log')
* ->run();
*
* // one line
* $this->_touch('.gitignore');
* $this->_mkdir('logs');
*
* ?>
* ```
*
* @method $this mkdir(string|array|\Traversable $dir, int $mode = 0777)
* @method $this touch(string|array|\Traversable $file, int $time = null, int $atime = null)
* @method $this copy(string $from, string $to, bool $force = false)
* @method $this chmod(string|array|\Traversable $file, int $permissions, int $umask = 0000, bool $recursive = false)
* @method $this chgrp(string|array|\Traversable $file, string $group, bool $recursive = false)
* @method $this chown(string|array|\Traversable $file, string $user, bool $recursive = false)
* @method $this remove(string|array|\Traversable $file)
* @method $this rename(string $from, string $to, bool $force = false)
* @method $this symlink(string $from, string $to, bool $copyOnWindows = false)
* @method $this mirror(string $from, string $to, \Traversable $iterator = null, array $options = [])
*/
class FilesystemStack extends StackBasedTask implements BuilderAwareInterface
{
use BuilderAwareTrait;
/**
* @var \Symfony\Component\Filesystem\Filesystem
*/
protected $fs;
public function __construct()
{
$this->fs = new sfFilesystem();
}
/**
* @return \Symfony\Component\Filesystem\Filesystem
*/
protected function getDelegate()
{
return $this->fs;
}
/**
* @param string $from
* @param string $to
* @param bool $force
*/
protected function _copy($from, $to, $force = false)
{
$this->fs->copy($from, $to, $force);
}
/**
* @param string|string[]|\Traversable $file
* @param int $permissions
* @param int $umask
* @param bool $recursive
*/
protected function _chmod($file, $permissions, $umask = 0000, $recursive = false)
{
$this->fs->chmod($file, $permissions, $umask, $recursive);
}
/**
* @param string|string[]|\Traversable $file
* @param string $group
* @param bool $recursive
*/
protected function _chgrp($file, $group, $recursive = null)
{
$this->fs->chgrp($file, $group, $recursive);
}
/**
* @param string|string[]|\Traversable $file
* @param string $user
* @param bool $recursive
*/
protected function _chown($file, $user, $recursive = null)
{
$this->fs->chown($file, $user, $recursive);
}
/**
* @param string $origin
* @param string $target
* @param bool $overwrite
*
* @return null|true|\Robo\Result
*/
protected function _rename($origin, $target, $overwrite = false)
{
// we check that target does not exist
if ((!$overwrite && is_readable($target)) || (file_exists($target) && !is_writable($target))) {
throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
}
// Due to a bug (limitation) in PHP, cross-volume renames do not work.
// See: https://bugs.php.net/bug.php?id=54097
if (true !== @rename($origin, $target)) {
return $this->crossVolumeRename($origin, $target);
}
return true;
}
/**
* @param string $origin
* @param string $target
*
* @return null|\Robo\Result
*/
protected function crossVolumeRename($origin, $target)
{
// First step is to try to get rid of the target. If there
// is a single, deletable file, then we will just unlink it.
if (is_file($target)) {
unlink($target);
}
// If the target still exists, we will try to delete it.
// TODO: Note that if this fails partway through, then we cannot
// adequately rollback. Perhaps we need to preflight the operation
// and determine if everything inside of $target is writable.
if (file_exists($target)) {
$this->fs->remove($target);
}
/** @var \Robo\Result $result */
$result = $this->collectionBuilder()->taskCopyDir([$origin => $target])->run();
if (!$result->wasSuccessful()) {
return $result;
}
$this->fs->remove($origin);
}
}
<?php
namespace Robo\Task\Testing;
use Robo\Contract\PrintedInterface;
use Robo\Task\BaseTask;
use Robo\Contract\CommandInterface;
/**
* Executes Phpspec tests
*
* ``` php
* <?php
* $this->taskPhpspec()
* ->format('pretty')
* ->noInteraction()
* ->run();
* ?>
* ```
*
*/
class Phpspec extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command;
/**
* @var string[] $formaters
* Available formaters for format option.
*/
protected $formaters = ['progress', 'html', 'pretty', 'junit', 'dot', 'tap'];
/**
* @var array $verbose_levels
* Available verbose levels.
*/
protected $verbose_levels = ['v', 'vv', 'vvv'];
/**
* Phpspec constructor.
*
* @param null|string $pathToPhpspec
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToPhpspec = null)
{
$this->command = $pathToPhpspec;
if (!$this->command) {
$this->command = $this->findExecutable('phpspec');
}
if (!$this->command) {
throw new \Robo\Exception\TaskException(__CLASS__, "Neither composer nor phar installation of Phpspec found");
}
$this->arg('run');
}
public function stopOnFail()
{
$this->option('stop-on-failure');
return $this;
}
public function noCodeGeneration()
{
$this->option('no-code-generation');
return $this;
}
public function quiet()
{
$this->option('quiet');
return $this;
}
/**
* @param string $level
*
* @return $this
*/
public function verbose($level = 'v')
{
if (!in_array($level, $this->verbose_levels)) {
throw new \InvalidArgumentException('expected ' . implode(',', $this->verbose_levels));
}
$this->option('-' . $level);
return $this;
}
/**
* @return $this
*/
public function noAnsi()
{
$this->option('no-ansi');
return $this;
}
/**
* @return $this
*/
public function noInteraction()
{
$this->option('no-interaction');
return $this;
}
/**
* @param string $config_file
*
* @return $this
*/
public function config($config_file)
{
$this->option('config', $config_file);
return $this;
}
/**
* @param string $formater
*
* @return $this
*/
public function format($formater)
{
if (!in_array($formater, $this->formaters)) {
throw new \InvalidArgumentException('expected ' . implode(',', $this->formaters));
}
$this->option('format', $formater);
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . $this->arguments;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running phpspec {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Testing;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Task\BaseTask;
/**
* Executes Behat tests
*
* ``` php
* <?php
* $this->taskBehat()
* ->format('pretty')
* ->noInteraction()
* ->run();
* ?>
* ```
*
*/
class Behat extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command;
/**
* @var string[] $formaters available formaters for format option
*/
protected $formaters = ['progress', 'pretty', 'junit'];
/**
* @var string[] $verbose_levels available verbose levels
*/
protected $verbose_levels = ['v', 'vv'];
/**
* Behat constructor.
*
* @param null|string $pathToBehat
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToBehat = null)
{
$this->command = $pathToBehat;
if (!$this->command) {
$this->command = $this->findExecutable('behat');
}
if (!$this->command) {
throw new \Robo\Exception\TaskException(__CLASS__, "Neither composer nor phar installation of Behat found");
}
}
/**
* @return $this
*/
public function stopOnFail()
{
$this->option('stop-on-failure');
return $this;
}
/**
* @return $this
*/
public function noInteraction()
{
$this->option('no-interaction');
return $this;
}
/**
* @param string $config_file
*
* @return $this
*/
public function config($config_file)
{
$this->option('config', $config_file);
return $this;
}
/**
* @return $this
*/
public function colors()
{
$this->option('colors');
return $this;
}
/**
* @return $this
*/
public function noColors()
{
$this->option('no-colors');
return $this;
}
/**
* @param string $suite
*
* @return $this
*/
public function suite($suite)
{
$this->option('suite', $suite);
return $this;
}
/**
* @param string $level
*
* @return $this
*/
public function verbose($level = 'v')
{
if (!in_array($level, $this->verbose_levels)) {
throw new \InvalidArgumentException('expected ' . implode(',', $this->verbose_levels));
}
$this->option('-' . $level);
return $this;
}
/**
* @param string $formater
*
* @return $this
*/
public function format($formater)
{
if (!in_array($formater, $this->formaters)) {
throw new \InvalidArgumentException('expected ' . implode(',', $this->formaters));
}
$this->option('format', $formater);
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . $this->arguments;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running behat {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Testing;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Task\BaseTask;
/**
* Runs [atoum](http://atoum.org/) tests
*
* ``` php
* <?php
* $this->taskAtoum()
* ->files('path/to/test.php')
* ->configFile('config/dev.php')
* ->run()
*
* ?>
* ```
*/
class Atoum extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command;
/**
* Atoum constructor.
*
* @param null|string $pathToAtoum
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToAtoum = null)
{
$this->command = $pathToAtoum;
if (!$this->command) {
$this->command = $this->findExecutable('atoum');
}
if (!$this->command) {
throw new \Robo\Exception\TaskException(__CLASS__, "Neither local atoum nor global composer installation not found");
}
}
/**
* Tag or Tags to filter.
*
* @param string|string[] $tags
*
* @return $this
*/
public function tags($tags)
{
return $this->addMultipleOption('tags', $tags);
}
/**
* Display result using the light reporter.
*
* @return $this
*/
public function lightReport()
{
$this->option("--use-light-report");
return $this;
}
/**
* Display result using the tap reporter.
*
* @return $this
*/
public function tap()
{
$this->option("use-tap-report");
return $this;
}
/**
* Path to the bootstrap file.
* @param string $file
*
* @return $this
*/
public function bootstrap($file)
{
$this->option("bootstrap", $file);
return $this;
}
/**
* Path to the config file.
*
* @param string $file
*
* @return $this
*/
public function configFile($file)
{
$this->option('-c', $file);
return $this;
}
/**
* Use atoum's debug mode.
*
* @return $this
*/
public function debug()
{
$this->option("debug");
return $this;
}
/**
* Test file or test files to run.
*
* @param string|string[]
*
* @return $this
*/
public function files($files)
{
return $this->addMultipleOption('f', $files);
}
/**
* Test directory or directories to run.
*
* @param string|string[]
* A single directory or a list of directories.
*
* @return $this
*/
public function directories($directories)
{
return $this->addMultipleOption('directories', $directories);
}
/**
* @param string $option
* @param string|string[] $values
*
* @return $this
*/
protected function addMultipleOption($option, $values)
{
if (is_string($values)) {
$values = [$values];
}
foreach ($values as $value) {
$this->option($option, $value);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . $this->arguments;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running atoum ' . $this->arguments);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Testing;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Task\BaseTask;
/**
* Runs PHPUnit tests
*
* ``` php
* <?php
* $this->taskPHPUnit()
* ->group('core')
* ->bootstrap('test/bootstrap.php')
* ->run()
*
* ?>
* ```
*/
class PHPUnit extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command;
/**
* Directory of test files or single test file to run. Appended to
* the command and arguments.
*
* @var string
*/
protected $files = '';
/**
* PHPUnit constructor.
*
* @param null|string $pathToPhpUnit
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToPhpUnit = null)
{
$this->command = $pathToPhpUnit;
if (!$this->command) {
$this->command = $this->findExecutablePhar('phpunit');
}
if (!$this->command) {
throw new \Robo\Exception\TaskException(__CLASS__, "Neither local phpunit nor global composer installation not found");
}
}
/**
* @param string $filter
*
* @return $this
*/
public function filter($filter)
{
$this->option('filter', $filter);
return $this;
}
/**
* @param string $group
*
* @return $this
*/
public function group($group)
{
$this->option("group", $group);
return $this;
}
/**
* @param string $group
*
* @return $this
*/
public function excludeGroup($group)
{
$this->option("exclude-group", $group);
return $this;
}
/**
* adds `log-json` option to runner
*
* @param string $file
*
* @return $this
*/
public function json($file = null)
{
$this->option("log-json", $file);
return $this;
}
/**
* adds `log-junit` option
*
* @param string $file
*
* @return $this
*/
public function xml($file = null)
{
$this->option("log-junit", $file);
return $this;
}
/**
* @param string $file
*
* @return $this
*/
public function tap($file = "")
{
$this->option("log-tap", $file);
return $this;
}
/**
* @param string $file
*
* @return $this
*/
public function bootstrap($file)
{
$this->option("bootstrap", $file);
return $this;
}
/**
* @param string $file
*
* @return $this
*/
public function configFile($file)
{
$this->option('-c', $file);
return $this;
}
/**
* @return $this
*/
public function debug()
{
$this->option("debug");
return $this;
}
/**
* Directory of test files or single test file to run.
*
* @param string $files
* A single test file or a directory containing test files.
*
* @return $this
*
* @throws \Robo\Exception\TaskException
*
* @deprecated Use file() or dir() method instead
*/
public function files($files)
{
if (!empty($this->files) || is_array($files)) {
throw new \Robo\Exception\TaskException(__CLASS__, "Only one file or directory may be provided.");
}
$this->files = ' ' . $files;
return $this;
}
/**
* Test the provided file.
*
* @param string $file
* Path to file to test.
*
* @return $this
*/
public function file($file)
{
return $this->files($file);
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . $this->arguments . $this->files;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running PHPUnit {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Testing;
use Robo\Contract\PrintedInterface;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
use Robo\Contract\CommandInterface;
use Symfony\Component\Process\Process;
/**
* Executes Codeception tests
*
* ``` php
* <?php
* // config
* $this->taskCodecept()
* ->suite('acceptance')
* ->env('chrome')
* ->group('admin')
* ->xml()
* ->html()
* ->run();
*
* ?>
* ```
*
*/
class Codecept extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command;
/**
* @param string $pathToCodeception
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToCodeception = '')
{
$this->command = $pathToCodeception;
if (!$this->command) {
$this->command = $this->findExecutable('codecept');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "Neither composer nor phar installation of Codeception found.");
}
$this->command .= ' run';
}
/**
* @param string $suite
*
* @return $this
*/
public function suite($suite)
{
$this->option(null, $suite);
return $this;
}
/**
* @param string $testName
*
* @return $this
*/
public function test($testName)
{
$this->option(null, $testName);
return $this;
}
/**
* set group option. Can be called multiple times
*
* @param string $group
*
* @return $this
*/
public function group($group)
{
$this->option("group", $group);
return $this;
}
/**
* @param string $group
*
* @return $this
*/
public function excludeGroup($group)
{
$this->option("skip-group", $group);
return $this;
}
/**
* generate json report
*
* @param string $file
*
* @return $this
*/
public function json($file = null)
{
$this->option("json", $file);
return $this;
}
/**
* generate xml JUnit report
*
* @param string $file
*
* @return $this
*/
public function xml($file = null)
{
$this->option("xml", $file);
return $this;
}
/**
* Generate html report
*
* @param string $dir
*
* @return $this
*/
public function html($dir = null)
{
$this->option("html", $dir);
return $this;
}
/**
* generate tap report
*
* @param string $file
*
* @return $this
*/
public function tap($file = null)
{
$this->option("tap", $file);
return $this;
}
/**
* provides config file other then default `codeception.yml` with `-c` option
*
* @param string $file
*
* @return $this
*/
public function configFile($file)
{
$this->option("-c", $file);
return $this;
}
/**
* collect codecoverage in raw format. You may pass name of cov file to save results
*
* @param null|string $cov
*
* @return $this
*/
public function coverage($cov = null)
{
$this->option("coverage", $cov);
return $this;
}
/**
* execute in silent mode
*
* @return $this
*/
public function silent()
{
$this->option("silent");
return $this;
}
/**
* collect code coverage in xml format. You may pass name of xml file to save results
*
* @param string $xml
*
* @return $this
*/
public function coverageXml($xml = null)
{
$this->option("coverage-xml", $xml);
return $this;
}
/**
* collect code coverage and generate html report. You may pass
*
* @param string $html
*
* @return $this
*/
public function coverageHtml($html = null)
{
$this->option("coverage-html", $html);
return $this;
}
/**
* @param string $env
*
* @return $this
*/
public function env($env)
{
$this->option("env", $env);
return $this;
}
/**
* @return $this
*/
public function debug()
{
$this->option("debug");
return $this;
}
/**
* @return $this
*/
public function noRebuild()
{
$this->option("no-rebuild");
return $this;
}
/**
* @param string $failGroup
* @return $this
*/
public function failGroup($failGroup)
{
$this->option('override', "extensions: config: Codeception\\Extension\\RunFailed: fail-group: {$failGroup}");
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return $this->command . $this->arguments;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
$this->printTaskInfo('Executing {command}', ['command' => $command]);
return $this->executeCommand($command);
}
}
<?php
namespace Robo\Task\Testing;
trait loadTasks
{
/**
* @param null|string $pathToCodeception
*
* @return \Robo\Task\Testing\Codecept|\Robo\Collection\CollectionBuilder
*/
protected function taskCodecept($pathToCodeception = null)
{
return $this->task(Codecept::class, $pathToCodeception);
}
/**
* @param null|string $pathToPhpUnit
*
* @return \Robo\Task\Testing\PHPUnit|\Robo\Collection\CollectionBuilder
*/
protected function taskPhpUnit($pathToPhpUnit = null)
{
return $this->task(PHPUnit::class, $pathToPhpUnit);
}
/**
* @param null|string $pathToPhpspec
*
* @return \Robo\Task\Testing\Phpspec|\Robo\Collection\CollectionBuilder
*/
protected function taskPhpspec($pathToPhpspec = null)
{
return $this->task(Phpspec::class, $pathToPhpspec);
}
/**
* @param null|string $pathToAtoum
*
* @return \Robo\Task\Testing\Atoum|\Robo\Collection\CollectionBuilder
*/
protected function taskAtoum($pathToAtoum = null)
{
return $this->task(Atoum::class, $pathToAtoum);
}
/**
* @param null|string $pathToBehat
*
* @return \Robo\Task\Testing\Behat|\Robo\Collection\CollectionBuilder
*/
protected function taskBehat($pathToBehat = null)
{
return $this->task(Behat::class, $pathToBehat);
}
}
<?php
namespace Robo\Task\ApiGen;
trait loadTasks
{
/**
* @param null|string $pathToApiGen
*
* @return \Robo\Task\ApiGen\ApiGen|\Robo\Collection\CollectionBuilder
*/
protected function taskApiGen($pathToApiGen = null)
{
return $this->task(ApiGen::class, $pathToApiGen);
}
}
<?php
namespace Robo\Task\ApiGen;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
use Traversable;
/**
* Executes ApiGen command to generate documentation
*
* ``` php
* <?php
* // ApiGen Command
* $this->taskApiGen('./vendor/apigen/apigen.phar')
* ->config('./apigen.neon')
* ->templateConfig('vendor/apigen/apigen/templates/bootstrap/config.neon')
* ->wipeout(true)
* ->run();
* ?>
* ```
*/
class ApiGen extends BaseTask implements CommandInterface
{
use \Robo\Common\ExecOneCommand;
const BOOL_NO = 'no';
const BOOL_YES = 'yes';
/**
* @var string
*/
protected $command;
/**
* @var string
*/
protected $operation = 'generate';
/**
* @param null|string $pathToApiGen
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToApiGen = null)
{
$this->command = $pathToApiGen;
$command_parts = [];
preg_match('/((?:.+)?apigen(?:\.phar)?) ?( \w+)? ?(.+)?/', $this->command, $command_parts);
if (count($command_parts) === 3) {
list(, $this->command, $this->operation) = $command_parts;
}
if (count($command_parts) === 4) {
list(, $this->command, $this->operation, $arg) = $command_parts;
$this->arg($arg);
}
if (!$this->command) {
$this->command = $this->findExecutablePhar('apigen');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "No apigen installation found");
}
}
/**
* Pass methods parameters as arguments to executable. Argument values
* are automatically escaped.
*
* @param string|string[] $args
*
* @return $this
*/
public function args($args)
{
if (!is_array($args)) {
$args = func_get_args();
}
$args = array_map(function ($arg) {
if (preg_match('/^\w+$/', trim($arg)) === 1) {
$this->operation = $arg;
return null;
}
return $arg;
}, $args);
$args = array_filter($args);
$this->arguments .= ' ' . implode(' ', array_map('static::escape', $args));
return $this;
}
/**
* @param array|\Traversable|string $arg
* A single object or something traversable.
*
* @return array|\Traversable
* The provided argument if it was already traversable, or the given
* argument returned as a one-element array.
*/
protected static function forceTraversable($arg)
{
$traversable = $arg;
if (!is_array($traversable) && !($traversable instanceof \Traversable)) {
$traversable = array($traversable);
}
return $traversable;
}
/**
* @param array|string $arg
* A single argument or an array of multiple string values.
*
* @return string
* A comma-separated string of all of the provided arguments, suitable as
* a command-line "list" type argument for ApiGen.
*/
protected static function asList($arg)
{
$normalized = is_array($arg) ? $arg : array($arg);
return implode(',', $normalized);
}
/**
* @param bool|string $val
* An argument to be normalized.
* @param string $default
* One of self::BOOL_YES or self::BOOK_NO if the provided value could not
* deterministically be converted to a yes or no value.
*
* @return string
* The given value as a command-line "yes|no" type of argument for ApiGen,
* or the default value if none could be determined.
*/
protected static function asTextBool($val, $default)
{
if ($val === self::BOOL_YES || $val === self::BOOL_NO) {
return $val;
}
if (!$val) {
return self::BOOL_NO;
}
if ($val === true) {
return self::BOOL_YES;
}
if (is_numeric($val) && $val != 0) {
return self::BOOL_YES;
}
if (strcasecmp($val[0], 'y') === 0) {
return self::BOOL_YES;
}
if (strcasecmp($val[0], 'n') === 0) {
return self::BOOL_NO;
}
// meh, good enough, let apigen sort it out
return $default;
}
/**
* @param string $config
*
* @return $this
*/
public function config($config)
{
$this->option('config', $config);
return $this;
}
/**
* @param array|string|\Traversable $src
* One or more source values.
*
* @return $this
*/
public function source($src)
{
foreach (self::forceTraversable($src) as $source) {
$this->option('source', $source);
}
return $this;
}
/**
* @param string $dest
*
* @return $this
*/
public function destination($dest)
{
$this->option('destination', $dest);
return $this;
}
/**
* @param array|string $exts
* One or more extensions.
*
* @return $this
*/
public function extensions($exts)
{
$this->option('extensions', self::asList($exts));
return $this;
}
/**
* @param array|string $exclude
* One or more exclusions.
*
* @return $this
*/
public function exclude($exclude)
{
foreach (self::forceTraversable($exclude) as $excl) {
$this->option('exclude', $excl);
}
return $this;
}
/**
* @param array|string|\Traversable $path
* One or more skip-doc-path values.
*
* @return $this
*/
public function skipDocPath($path)
{
foreach (self::forceTraversable($path) as $skip) {
$this->option('skip-doc-path', $skip);
}
return $this;
}
/**
* @param array|string|\Traversable $prefix
* One or more skip-doc-prefix values.
*
* @return $this
*/
public function skipDocPrefix($prefix)
{
foreach (self::forceTraversable($prefix) as $skip) {
$this->option('skip-doc-prefix', $skip);
}
return $this;
}
/**
* @param array|string $charset
* One or more charsets.
*
* @return $this
*/
public function charset($charset)
{
$this->option('charset', self::asList($charset));
return $this;
}
/**
* @param string $name
*
* @return $this
*/
public function mainProjectNamePrefix($name)
{
$this->option('main', $name);
return $this;
}
/**
* @param string $title
*
* @return $this
*/
public function title($title)
{
$this->option('title', $title);
return $this;
}
/**
* @param string $baseUrl
*
* @return $this
*/
public function baseUrl($baseUrl)
{
$this->option('base-url', $baseUrl);
return $this;
}
/**
* @param string $id
*
* @return $this
*/
public function googleCseId($id)
{
$this->option('google-cse-id', $id);
return $this;
}
/**
* @param string $trackingCode
*
* @return $this
*/
public function googleAnalytics($trackingCode)
{
$this->option('google-analytics', $trackingCode);
return $this;
}
/**
* @param mixed $templateConfig
*
* @return $this
*/
public function templateConfig($templateConfig)
{
$this->option('template-config', $templateConfig);
return $this;
}
/**
* @param array|string $tags
* One or more supported html tags.
*
* @return $this
*/
public function allowedHtml($tags)
{
$this->option('allowed-html', self::asList($tags));
return $this;
}
/**
* @param string $groups
*
* @return $this
*/
public function groups($groups)
{
$this->option('groups', $groups);
return $this;
}
/**
* @param array|string $types
* One or more supported autocomplete types.
*
* @return $this
*/
public function autocomplete($types)
{
$this->option('autocomplete', self::asList($types));
return $this;
}
/**
* @param array|string $levels
* One or more access levels.
*
* @return $this
*/
public function accessLevels($levels)
{
$this->option('access-levels', self::asList($levels));
return $this;
}
/**
* @param boolean|string $internal
* 'yes' or true if internal, 'no' or false if not.
*
* @return $this
*/
public function internal($internal)
{
$this->option('internal', self::asTextBool($internal, self::BOOL_NO));
return $this;
}
/**
* @param bool|string $php
* 'yes' or true to generate documentation for internal php classes, 'no'
* or false otherwise.
*
* @return $this
*/
public function php($php)
{
$this->option('php', self::asTextBool($php, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $tree
* 'yes' or true to generate a tree view of classes, 'no' or false
* otherwise.
*
* @return $this
*/
public function tree($tree)
{
$this->option('tree', self::asTextBool($tree, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $dep
* 'yes' or true to generate documentation for deprecated classes, 'no' or
* false otherwise.
*
* @return $this
*/
public function deprecated($dep)
{
$this->option('deprecated', self::asTextBool($dep, self::BOOL_NO));
return $this;
}
/**
* @param bool|string $todo
* 'yes' or true to document tasks, 'no' or false otherwise.
*
* @return $this
*/
public function todo($todo)
{
$this->option('todo', self::asTextBool($todo, self::BOOL_NO));
return $this;
}
/**
* @param bool|string $src
* 'yes' or true to generate highlighted source code, 'no' or false
* otherwise.
*
* @return $this
*/
public function sourceCode($src)
{
$this->option('source-code', self::asTextBool($src, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $zipped
* 'yes' or true to generate downloadable documentation, 'no' or false
* otherwise.
*
* @return $this
*/
public function download($zipped)
{
$this->option('download', self::asTextBool($zipped, self::BOOL_NO));
return $this;
}
/**
* @param string $path
*
* @return $this
*/
public function report($path)
{
$this->option('report', $path);
return $this;
}
/**
* @param bool|string $wipeout
* 'yes' or true to clear out the destination directory, 'no' or false
* otherwise.
*
* @return $this
*/
public function wipeout($wipeout)
{
$this->option('wipeout', self::asTextBool($wipeout, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $quiet
* 'yes' or true for quiet, 'no' or false otherwise.
*
* @return $this
*/
public function quiet($quiet)
{
$this->option('quiet', self::asTextBool($quiet, self::BOOL_NO));
return $this;
}
/**
* @param bool|string $bar
* 'yes' or true to display a progress bar, 'no' or false otherwise.
*
* @return $this
*/
public function progressbar($bar)
{
$this->option('progressbar', self::asTextBool($bar, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $colors
* 'yes' or true colorize the output, 'no' or false otherwise.
*
* @return $this
*/
public function colors($colors)
{
$this->option('colors', self::asTextBool($colors, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $check
* 'yes' or true to check for updates, 'no' or false otherwise.
*
* @return $this
*/
public function updateCheck($check)
{
$this->option('update-check', self::asTextBool($check, self::BOOL_YES));
return $this;
}
/**
* @param bool|string $debug
* 'yes' or true to enable debug mode, 'no' or false otherwise.
*
* @return $this
*/
public function debug($debug)
{
$this->option('debug', self::asTextBool($debug, self::BOOL_NO));
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return "$this->command $this->operation$this->arguments";
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running ApiGen {args}', ['args' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Vcs;
use Robo\Task\CommandStack;
/**
* Runs hg commands in stack. You can use `stopOnFail()` to point that stack should be terminated on first fail.
*
* ``` php
* <?php
* $this->hgStack
* ->cloneRepo('https://bitbucket.org/durin42/hgsubversion')
* ->pull()
* ->add()
* ->commit('changed')
* ->push()
* ->tag('0.6.0')
* ->push('0.6.0')
* ->run();
* ?>
* ```
*/
class HgStack extends CommandStack
{
/**
* @param string $pathToHg
*/
public function __construct($pathToHg = 'hg')
{
$this->executable = $pathToHg;
}
/**
* Executes `hg clone`
*
* @param string $repo
* @param string $to
*
* @return $this
*/
public function cloneRepo($repo, $to = '')
{
return $this->exec(['clone', $repo, $to]);
}
/**
* Executes `hg add` command with files to add by pattern
*
* @param string $include
* @param string $exclude
*
* @return $this
*/
public function add($include = '', $exclude = '')
{
if (strlen($include) > 0) {
$include = "-I {$include}";
}
if (strlen($exclude) > 0) {
$exclude = "-X {$exclude}";
}
return $this->exec([__FUNCTION__, $include, $exclude]);
}
/**
* Executes `hg commit` command with a message
*
* @param string $message
* @param string $options
*
* @return $this
*/
public function commit($message, $options = '')
{
return $this->exec([__FUNCTION__, "-m '{$message}'", $options]);
}
/**
* Executes `hg pull` command.
*
* @param string $branch
*
* @return $this
*/
public function pull($branch = '')
{
if (strlen($branch) > 0) {
$branch = "-b '{$branch}''";
}
return $this->exec([__FUNCTION__, $branch]);
}
/**
* Executes `hg push` command
*
* @param string $branch
*
* @return $this
*/
public function push($branch = '')
{
if (strlen($branch) > 0) {
$branch = "-b '{$branch}'";
}
return $this->exec([__FUNCTION__, $branch]);
}
/**
* Performs hg merge
*
* @param string $revision
*
* @return $this
*/
public function merge($revision = '')
{
if (strlen($revision) > 0) {
$revision = "-r {$revision}";
}
return $this->exec([__FUNCTION__, $revision]);
}
/**
* Executes `hg tag` command
*
* @param string $tag_name
* @param string $message
*
* @return $this
*/
public function tag($tag_name, $message = '')
{
if ($message !== '') {
$message = "-m '{$message}'";
}
return $this->exec([__FUNCTION__, $message, $tag_name]);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running hg commands...');
return parent::run();
}
}
<?php
namespace Robo\Task\Vcs;
trait loadShortcuts
{
/**
* @param string $url
*
* @return \Robo\Result
*/
protected function _svnCheckout($url)
{
return $this->taskSvnStack()->checkout($url)->run();
}
/**
* @param string $url
*
* @return \Robo\Result
*/
protected function _gitClone($url)
{
return $this->taskGitStack()->cloneRepo($url)->run();
}
/**
* @param string $url
*
* @return \Robo\Result
*/
protected function _hgClone($url)
{
return $this->taskHgStack()->cloneRepo($url)->run();
}
}
<?php
namespace Robo\Task\Vcs;
use Robo\Contract\CommandInterface;
use Robo\Result;
use Robo\Task\CommandStack;
/**
* Runs Svn commands in stack. You can use `stopOnFail()` to point that stack should be terminated on first fail.
*
* ``` php
* <?php
* $this->taskSvnStack()
* ->checkout('http://svn.collab.net/repos/svn/trunk')
* ->run()
*
* // alternatively
* $this->_svnCheckout('http://svn.collab.net/repos/svn/trunk');
*
* $this->taskSvnStack('username', 'password')
* ->stopOnFail()
* ->update()
* ->add('doc/*')
* ->commit('doc updated')
* ->run();
* ?>
* ```
*/
class SvnStack extends CommandStack implements CommandInterface
{
/**
* @var bool
*/
protected $stopOnFail = false;
/**
* {@inheritdoc}
*/
protected $result;
/**
* @param string $username
* @param string $password
* @param string $pathToSvn
*/
public function __construct($username = '', $password = '', $pathToSvn = 'svn')
{
$this->executable = $pathToSvn;
if (!empty($username)) {
$this->executable .= " --username $username";
}
if (!empty($password)) {
$this->executable .= " --password $password";
}
$this->result = Result::success($this);
}
/**
* Updates `svn update` command
*
* @param string $path
*
* @return $this
*/
public function update($path = '')
{
return $this->exec("update $path");
}
/**
* Executes `svn add` command with files to add pattern
*
* @param string $pattern
*
* @return $this
*/
public function add($pattern = '')
{
return $this->exec("add $pattern");
}
/**
* Executes `svn commit` command with a message
*
* @param string $message
* @param string $options
*
* @return $this
*/
public function commit($message, $options = "")
{
return $this->exec("commit -m '$message' $options");
}
/**
* Executes `svn checkout` command
*
* @param string $branch
*
* @return $this
*/
public function checkout($branch)
{
return $this->exec("checkout $branch");
}
}
<?php
namespace Robo\Task\Vcs;
trait loadTasks
{
/**
* @param string $username
* @param string $password
* @param string $pathToSvn
*
* @return \Robo\Task\Vcs\SvnStack|\Robo\Collection\CollectionBuilder
*/
protected function taskSvnStack($username = '', $password = '', $pathToSvn = 'svn')
{
return $this->task(SvnStack::class, $username, $password, $pathToSvn);
}
/**
* @param string $pathToGit
*
* @return \Robo\Task\Vcs\GitStack|\Robo\Collection\CollectionBuilder
*/
protected function taskGitStack($pathToGit = 'git')
{
return $this->task(GitStack::class, $pathToGit);
}
/**
* @param string $pathToHg
*
* @return \Robo\Task\Vcs\HgStack|\Robo\Collection\CollectionBuilder
*/
protected function taskHgStack($pathToHg = 'hg')
{
return $this->task(HgStack::class, $pathToHg);
}
}
<?php
namespace Robo\Task\Vcs;
use Robo\Task\CommandStack;
use Robo\Common\ProcessUtils;
/**
* Runs Git commands in stack. You can use `stopOnFail()` to point that stack should be terminated on first fail.
*
* ``` php
* <?php
* $this->taskGitStack()
* ->stopOnFail()
* ->add('-A')
* ->commit('adding everything')
* ->push('origin','master')
* ->tag('0.6.0')
* ->push('origin','0.6.0')
* ->run()
*
* $this->taskGitStack()
* ->stopOnFail()
* ->add('doc/*')
* ->commit('doc updated')
* ->push()
* ->run();
* ?>
* ```
*/
class GitStack extends CommandStack
{
/**
* @param string $pathToGit
*/
public function __construct($pathToGit = 'git')
{
$this->executable = $pathToGit;
}
/**
* Executes `git clone`
*
* @param string $repo
* @param string $to
* @param string $branch
*
* @return $this
*/
public function cloneRepo($repo, $to = "", $branch = "")
{
$cmd = ['clone', $repo, $to];
if (!empty($branch)) {
$cmd[] = "--branch $branch";
}
return $this->exec($cmd);
}
/**
* Executes `git clone` with depth 1 as default
*
* @param string $repo
* @param string $to
* @param string $branch
* @param int $depth
*
* @return $this
*/
public function cloneShallow($repo, $to = '', $branch = "", $depth = 1)
{
$cmd = ["clone --depth $depth", $repo, $to];
if (!empty($branch)) {
$cmd[] = "--branch $branch";
}
return $this->exec($cmd);
}
/**
* Executes `git add` command with files to add pattern
*
* @param string $pattern
*
* @return $this
*/
public function add($pattern)
{
return $this->exec([__FUNCTION__, $pattern]);
}
/**
* Executes `git commit` command with a message
*
* @param string $message
* @param string $options
*
* @return $this
*/
public function commit($message, $options = "")
{
$message = ProcessUtils::escapeArgument($message);
return $this->exec([__FUNCTION__, "-m $message", $options]);
}
/**
* Executes `git pull` command.
*
* @param string $origin
* @param string $branch
*
* @return $this
*/
public function pull($origin = '', $branch = '')
{
return $this->exec([__FUNCTION__, $origin, $branch]);
}
/**
* Executes `git push` command
*
* @param string $origin
* @param string $branch
*
* @return $this
*/
public function push($origin = '', $branch = '')
{
return $this->exec([__FUNCTION__, $origin, $branch]);
}
/**
* Performs git merge
*
* @param string $branch
*
* @return $this
*/
public function merge($branch)
{
return $this->exec([__FUNCTION__, $branch]);
}
/**
* Executes `git checkout` command
*
* @param string $branch
*
* @return $this
*/
public function checkout($branch)
{
return $this->exec([__FUNCTION__, $branch]);
}
/**
* Executes `git tag` command
*
* @param string $tag_name
* @param string $message
*
* @return $this
*/
public function tag($tag_name, $message = "")
{
if ($message != "") {
$message = "-m '$message'";
}
return $this->exec([__FUNCTION__, $message, $tag_name]);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo("Running git commands...");
return parent::run();
}
}
<?php
namespace Robo\Task;
use Robo\Result;
/**
* Extend StackBasedTask to create a Robo task that
* runs a sequence of commands.
*
* This is particularly useful for wrapping an existing
* object-oriented API. Doing it this way requires
* less code than manually adding a method for each wrapped
* function in the delegate. Additionally, wrapping the
* external class in a StackBasedTask creates a loosely-coupled
* interface -- i.e. if a new method is added to the delegate
* class, it is not necessary to update your wrapper, as the
* new functionality will be inherited.
*
* For example, you have:
*
* $frobinator = new Frobinator($a, $b, $c)
* ->friz()
* ->fraz()
* ->frob();
*
* We presume that the existing library throws an exception on error.
*
* You want:
*
* $result = $this->taskFrobinator($a, $b, $c)
* ->friz()
* ->fraz()
* ->frob()
* ->run();
*
* Execution is deferred until run(), and a Robo\Result instance is
* returned. Additionally, using Robo will covert Exceptions
* into RoboResult objects.
*
* To create a new Robo task:
*
* - Make a new class that extends StackBasedTask
* - Give it a constructor that creates a new Frobinator
* - Override getDelegate(), and return the Frobinator instance
*
* Finally, add your new class to loadTasks.php as usual,
* and you are all done.
*
* If you need to add any methods to your task that should run
* immediately (e.g. to set parameters used at run() time), just
* implement them in your derived class.
*
* If you need additional methods that should run deferred, just
* define them as 'protected function _foo()'. Then, users may
* call $this->taskFrobinator()->foo() to get deferred execution
* of _foo().
*/
abstract class StackBasedTask extends BaseTask
{
/**
* @var array
*/
protected $stack = [];
/**
* @var bool
*/
protected $stopOnFail = true;
/**
* @param bool $stop
*
* @return $this
*/
public function stopOnFail($stop = true)
{
$this->stopOnFail = $stop;
return $this;
}
/**
* Derived classes should override the getDelegate() method, and
* return an instance of the API class being wrapped. When this
* is done, any method of the delegate is available as a method of
* this class. Calling one of the delegate's methods will defer
* execution until the run() method is called.
*
* @return null|object
*/
protected function getDelegate()
{
return null;
}
/**
* Derived classes that have more than one delegate may override
* getCommandList to add as many delegate commands as desired to
* the list of potential functions that __call() tried to find.
*
* @param string $function
*
* @return array
*/
protected function getDelegateCommandList($function)
{
return [[$this, "_$function"], [$this->getDelegate(), $function]];
}
/**
* Print progress about the commands being executed
*
* @param string $command
* @param string $action
*/
protected function printTaskProgress($command, $action)
{
$this->printTaskInfo('{command} {action}', ['command' => "{$command[1]}", 'action' => json_encode($action, JSON_UNESCAPED_SLASHES)]);
}
/**
* Derived classes can override processResult to add more
* logic to result handling from functions. By default, it
* is assumed that if a function returns in int, then
* 0 == success, and any other value is the error code.
*
* @param int|\Robo\Result $function_result
*
* @return \Robo\Result
*/
protected function processResult($function_result)
{
if (is_int($function_result)) {
if ($function_result) {
return Result::error($this, $function_result);
}
}
return Result::success($this);
}
/**
* Record a function to call later.
*
* @param string $command
* @param array $args
*
* @return $this
*/
protected function addToCommandStack($command, $args)
{
$this->stack[] = array_merge([$command], $args);
return $this;
}
/**
* Any API function provided by the delegate that executes immediately
* may be handled by __call automatically. These operations will all
* be deferred until this task's run() method is called.
*
* @param string $function
* @param array $args
*
* @return $this
*/
public function __call($function, $args)
{
foreach ($this->getDelegateCommandList($function) as $command) {
if (method_exists($command[0], $command[1])) {
// Otherwise, we'll defer calling this function
// until run(), and return $this.
$this->addToCommandStack($command, $args);
return $this;
}
}
$message = "Method $function does not exist.\n";
throw new \BadMethodCallException($message);
}
/**
* @return int
*/
public function progressIndicatorSteps()
{
// run() will call advanceProgressIndicator() once for each
// file, one after calling stopBuffering, and again after compression.
return count($this->stack);
}
/**
* Run all of the queued objects on the stack
*
* @return \Robo\Result
*/
public function run()
{
$this->startProgressIndicator();
$result = Result::success($this);
foreach ($this->stack as $action) {
$command = array_shift($action);
$this->printTaskProgress($command, $action);
$this->advanceProgressIndicator();
// TODO: merge data from the result on this call
// with data from the result on the previous call?
// For now, the result always comes from the last function.
$result = $this->callTaskMethod($command, $action);
if ($this->stopOnFail && $result && !$result->wasSuccessful()) {
break;
}
}
$this->stopProgressIndicator();
// todo: add timing information to the result
return $result;
}
/**
* Execute one task method
*
* @param string $command
* @param array $action
*
* @return \Robo\Result
*/
protected function callTaskMethod($command, $action)
{
try {
$function_result = call_user_func_array($command, $action);
return $this->processResult($function_result);
} catch (\Exception $e) {
$this->printTaskError($e->getMessage());
return Result::fromException($this, $e);
}
}
}
<?php
namespace Robo\Task\Assets;
use Robo\Result;
/**
* Compiles scss files.
*
* ```php
* <?php
* $this->taskScss([
* 'scss/default.scss' => 'css/default.css'
* ])
* ->importDir('assets/styles')
* ->run();
* ?>
* ```
*
* Use the following scss compiler in your project:
*
* ```
* "scssphp/scssphp ": "~1.0.0",
* ```
*
* You can implement additional compilers by extending this task and adding a
* method named after them and overloading the scssCompilers() method to
* inject the name there.
*/
class Scss extends CssPreprocessor
{
const FORMAT_NAME = 'scss';
/**
* @var string[]
*/
protected $compilers = [
'scssphp', // https://github.com/scssphp/scssphp
];
/**
* scssphp compiler
* @link https://github.com/scssphp/scssphp
*
* @param string $file
*
* @return string
*/
protected function scssphp($file)
{
if (!class_exists('\ScssPhp\ScssPhp\Compiler')) {
return Result::errorMissingPackage($this, 'scssphp', 'scssphp/scssphp');
}
$scssCode = file_get_contents($file);
$scss = new \ScssPhp\ScssPhp\Compiler();
// set options for the scssphp compiler
if (isset($this->compilerOptions['importDirs'])) {
$scss->setImportPaths($this->compilerOptions['importDirs']);
}
if (isset($this->compilerOptions['formatter'])) {
$scss->setFormatter($this->compilerOptions['formatter']);
}
return $scss->compile($scssCode);
}
/**
* Sets the formatter for scssphp
*
* The method setFormatter($formatterName) sets the current formatter to $formatterName,
* the name of a class as a string that implements the formatting interface. See the source
* for ScssPhp\ScssPhp\Formatter\Expanded for an example.
*
* Five formatters are included with scssphp/scssphp:
* - ScssPhp\ScssPhp\Formatter\Expanded
* - ScssPhp\ScssPhp\Formatter\Nested (default)
* - ScssPhp\ScssPhp\Formatter\Compressed
* - ScssPhp\ScssPhp\Formatter\Compact
* - ScssPhp\ScssPhp\Formatter\Crunched
*
* @link https://scssphp.github.io/scssphp/docs/#output-formatting
*
* @param string $formatterName
*
* @return $this
*/
public function setFormatter($formatterName)
{
return parent::setFormatter($formatterName);
}
}
<?php
namespace Robo\Task\Assets;
use Robo\Result;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
use Robo\Task\Base\Exec;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Filesystem\Filesystem as sfFilesystem;
/**
* Minifies images. When the required minifier is not installed on the system
* the task will try to download it from the [imagemin](https://github.com/imagemin) repository.
*
* When the task is run without any specified minifier it will compress the images
* based on the extension.
*
* ```php
* $this->taskImageMinify('assets/images/*')
* ->to('dist/images/')
* ->run();
* ```
*
* This will use the following minifiers:
*
* - PNG: optipng
* - GIF: gifsicle
* - JPG, JPEG: jpegtran
* - SVG: svgo
*
* When the minifier is specified the task will use that for all the input files. In that case
* it is useful to filter the files with the extension:
*
* ```php
* $this->taskImageMinify('assets/images/*.png')
* ->to('dist/images/')
* ->minifier('pngcrush');
* ->run();
* ```
*
* The task supports the following minifiers:
*
* - optipng
* - pngquant
* - advpng
* - pngout
* - zopflipng
* - pngcrush
* - gifsicle
* - jpegoptim
* - jpeg-recompress
* - jpegtran
* - svgo (only minification, no downloading)
*
* You can also specifiy extra options for the minifiers:
*
* ```php
* $this->taskImageMinify('assets/images/*.jpg')
* ->to('dist/images/')
* ->minifier('jpegtran', ['-progressive' => null, '-copy' => 'none'])
* ->run();
* ```
*
* This will execute as:
* `jpegtran -copy none -progressive -optimize -outfile "dist/images/test.jpg" "/var/www/test/assets/images/test.jpg"`
*/
class ImageMinify extends BaseTask
{
/**
* Destination directory for the minified images.
*
* @var string
*/
protected $to;
/**
* Array of the source files.
*
* @var array
*/
protected $dirs = [];
/**
* Symfony 2 filesystem.
*
* @var sfFilesystem
*/
protected $fs;
/**
* Target directory for the downloaded binary executables.
*
* @var string
*/
protected $executableTargetDir;
/**
* Array for the downloaded binary executables.
*
* @var array
*/
protected $executablePaths = [];
/**
* Array for the individual results of all the files.
*
* @var array
*/
protected $results = [];
/**
* Default minifier to use.
*
* @var string
*/
protected $minifier;
/**
* Array for minifier options.
*
* @var array
*/
protected $minifierOptions = [];
/**
* Supported minifiers.
*
* @var array
*/
protected $minifiers = [
// Default 4
'optipng',
'gifsicle',
'jpegtran',
'svgo',
// PNG
'pngquant',
'advpng',
'pngout',
'zopflipng',
'pngcrush',
// JPG
'jpegoptim',
'jpeg-recompress',
];
/**
* Binary repositories of Imagemin.
*
* @link https://github.com/imagemin
*
* @var string[]
*/
protected $imageminRepos = [
// PNG
'optipng' => 'https://github.com/imagemin/optipng-bin',
'pngquant' => 'https://github.com/imagemin/pngquant-bin',
'advpng' => 'https://github.com/imagemin/advpng-bin',
'pngout' => 'https://github.com/imagemin/pngout-bin',
'zopflipng' => 'https://github.com/imagemin/zopflipng-bin',
'pngcrush' => 'https://github.com/imagemin/pngcrush-bin',
// Gif
'gifsicle' => 'https://github.com/imagemin/gifsicle-bin',
// JPG
'jpegtran' => 'https://github.com/imagemin/jpegtran-bin',
'jpegoptim' => 'https://github.com/imagemin/jpegoptim-bin',
'cjpeg' => 'https://github.com/imagemin/mozjpeg-bin', // note: we do not support this minifier because it creates JPG from non-JPG files
'jpeg-recompress' => 'https://github.com/imagemin/jpeg-recompress-bin',
// WebP
'cwebp' => 'https://github.com/imagemin/cwebp-bin', // note: we do not support this minifier because it creates WebP from non-WebP files
];
/**
* @param string|string[] $dirs
*/
public function __construct($dirs)
{
is_array($dirs)
? $this->dirs = $dirs
: $this->dirs[] = $dirs;
$this->fs = new sfFilesystem();
// guess the best path for the executables based on __DIR__
if (($pos = strpos(__DIR__, 'consolidation/robo')) !== false) {
// the executables should be stored in vendor/bin
$this->executableTargetDir = substr(__DIR__, 0, $pos) . 'bin';
}
// check if the executables are already available
foreach ($this->imageminRepos as $exec => $url) {
$path = $this->executableTargetDir . '/' . $exec;
// if this is Windows add a .exe extension
if (substr($this->getOS(), 0, 3) == 'win') {
$path .= '.exe';
}
if (is_file($path)) {
$this->executablePaths[$exec] = $path;
}
}
}
/**
* {@inheritdoc}
*/
public function run()
{
// find the files
$files = $this->findFiles($this->dirs);
// minify the files
$result = $this->minify($files);
// check if there was an error
if ($result instanceof Result) {
return $result;
}
$amount = (count($files) == 1 ? 'image' : 'images');
$message = "Minified {filecount} out of {filetotal} $amount into {destination}";
$context = ['filecount' => count($this->results['success']), 'filetotal' => count($files), 'destination' => $this->to];
if (count($this->results['success']) == count($files)) {
$this->printTaskSuccess($message, $context);
return Result::success($this, $message, $context);
} else {
return Result::error($this, $message, $context);
}
}
/**
* Sets the target directory where the files will be copied to.
*
* @param string $target
*
* @return $this
*/
public function to($target)
{
$this->to = rtrim($target, '/');
return $this;
}
/**
* Sets the minifier.
*
* @param string $minifier
* @param array $options
*
* @return $this
*/
public function minifier($minifier, array $options = [])
{
$this->minifier = $minifier;
$this->minifierOptions = array_merge($this->minifierOptions, $options);
return $this;
}
/**
* @param string[] $dirs
*
* @return array|\Robo\Result
*
* @throws \Robo\Exception\TaskException
*/
protected function findFiles($dirs)
{
$files = array();
// find the files
foreach ($dirs as $k => $v) {
// reset finder
$finder = new Finder();
$dir = $k;
$to = $v;
// check if target was given with the to() method instead of key/value pairs
if (is_int($k)) {
$dir = $v;
if (isset($this->to)) {
$to = $this->to;
} else {
throw new TaskException($this, 'target directory is not defined');
}
}
try {
$finder->files()->in($dir);
} catch (\InvalidArgumentException $e) {
// if finder cannot handle it, try with in()->name()
if (strpos($dir, '/') === false) {
$dir = './' . $dir;
}
$parts = explode('/', $dir);
$new_dir = implode('/', array_slice($parts, 0, -1));
try {
$finder->files()->in($new_dir)->name(array_pop($parts));
} catch (\InvalidArgumentException $e) {
return Result::fromException($this, $e);
}
}
foreach ($finder as $file) {
// store the absolute path as key and target as value in the files array
$files[$file->getRealpath()] = $this->getTarget($file->getRealPath(), $to);
}
$fileNoun = count($finder) == 1 ? ' file' : ' files';
$this->printTaskInfo("Found {filecount} $fileNoun in {dir}", ['filecount' => count($finder), 'dir' => $dir]);
}
return $files;
}
/**
* @param string $file
* @param string $to
*
* @return string
*/
protected function getTarget($file, $to)
{
$target = $to . '/' . basename($file);
return $target;
}
/**
* @param string[] $files
*
* @return \Robo\Result
*/
protected function minify($files)
{
// store the individual results into the results array
$this->results = [
'success' => [],
'error' => [],
];
// loop through the files
foreach ($files as $from => $to) {
$minifier = '';
if (!isset($this->minifier)) {
// check filetype based on the extension
$extension = strtolower(pathinfo($from, PATHINFO_EXTENSION));
// set the default minifiers based on the extension
switch ($extension) {
case 'png':
$minifier = 'optipng';
break;
case 'jpg':
case 'jpeg':
$minifier = 'jpegtran';
break;
case 'gif':
$minifier = 'gifsicle';
break;
case 'svg':
$minifier = 'svgo';
break;
}
} else {
if (!in_array($this->minifier, $this->minifiers, true)
&& !is_callable(strtr($this->minifier, '-', '_'))
) {
$message = sprintf('Invalid minifier %s!', $this->minifier);
return Result::error($this, $message);
}
$minifier = $this->minifier;
}
// Convert minifier name to camelCase (e.g. jpeg-recompress)
$funcMinifier = $this->camelCase($minifier);
// call the minifier method which prepares the command
if (is_callable($funcMinifier)) {
$command = call_user_func($funcMinifier, $from, $to, $this->minifierOptions);
} elseif (method_exists($this, $funcMinifier)) {
$command = $this->{$funcMinifier}($from, $to);
} else {
$message = sprintf('Minifier method <info>%s</info> cannot be found!', $funcMinifier);
return Result::error($this, $message);
}
// launch the command
$this->printTaskInfo('Minifying {filepath} with {minifier}', ['filepath' => $from, 'minifier' => $minifier]);
$result = $this->executeCommand($command);
// check the return code
if ($result->getExitCode() == 127) {
$this->printTaskError('The {minifier} executable cannot be found', ['minifier' => $minifier]);
// try to install from imagemin repository
if (array_key_exists($minifier, $this->imageminRepos)) {
$result = $this->installFromImagemin($minifier);
if ($result instanceof Result) {
if ($result->wasSuccessful()) {
$this->printTaskSuccess($result->getMessage());
// retry the conversion with the downloaded executable
if (is_callable($minifier)) {
$command = call_user_func($minifier, $from, $to, $this->minifierOptions);
} elseif (method_exists($this, $minifier)) {
$command = $this->{$minifier}($from, $to);
}
// launch the command
$this->printTaskInfo('Minifying {filepath} with {minifier}', ['filepath' => $from, 'minifier' => $minifier]);
$result = $this->executeCommand($command);
} else {
$this->printTaskError($result->getMessage());
// the download was not successful
return $result;
}
}
} else {
return $result;
}
}
// check the success of the conversion
if ($result->getExitCode() !== 0) {
$this->results['error'][] = $from;
} else {
$this->results['success'][] = $from;
}
}
}
/**
* @return string
*/
protected function getOS()
{
$os = php_uname('s');
$os .= '/' . php_uname('m');
// replace x86_64 to x64, because the imagemin repo uses that
$os = str_replace('x86_64', 'x64', $os);
// replace i386, i686, etc to x86, because of imagemin
$os = preg_replace('/i[0-9]86/', 'x86', $os);
// turn info to lowercase, because of imagemin
$os = strtolower($os);
return $os;
}
/**
* @param string $command
*
* @return \Robo\Result
*/
protected function executeCommand($command)
{
// insert the options into the command
$a = explode(' ', $command);
$executable = array_shift($a);
foreach ($this->minifierOptions as $key => $value) {
// first prepend the value
if (!empty($value)) {
array_unshift($a, $value);
}
// then add the key
if (!is_numeric($key)) {
array_unshift($a, $key);
}
}
// check if the executable can be replaced with the downloaded one
if (array_key_exists($executable, $this->executablePaths)) {
$executable = $this->executablePaths[$executable];
}
array_unshift($a, $executable);
$command = implode(' ', $a);
// execute the command
$exec = new Exec($command);
return $exec->inflect($this)->printed(false)->run();
}
/**
* @param string $executable
*
* @return \Robo\Result
*/
protected function installFromImagemin($executable)
{
// check if there is an url defined for the executable
if (!array_key_exists($executable, $this->imageminRepos)) {
$message = sprintf('The executable %s cannot be found in the defined imagemin repositories', $executable);
return Result::error($this, $message);
}
$this->printTaskInfo('Downloading the {executable} executable from the imagemin repository', ['executable' => $executable]);
$os = $this->getOS();
$url = $this->imageminRepos[$executable] . '/blob/master/vendor/' . $os . '/' . $executable . '?raw=true';
if (substr($os, 0, 3) == 'win') {
// if it is win, add a .exe extension
$url = $this->imageminRepos[$executable] . '/blob/master/vendor/' . $os . '/' . $executable . '.exe?raw=true';
}
$data = @file_get_contents($url, false, null);
if ($data === false) {
// there is something wrong with the url, try it without the version info
$url = preg_replace('/x[68][64]\//', '', $url);
$data = @file_get_contents($url, false, null);
if ($data === false) {
// there is still something wrong with the url if it is win, try with win32
if (substr($os, 0, 3) == 'win') {
$url = preg_replace('win/', 'win32/', $url);
$data = @file_get_contents($url, false, null);
if ($data === false) {
// there is nothing more we can do
$message = sprintf('Could not download the executable <info>%s</info>', $executable);
return Result::error($this, $message);
}
}
// if it is not windows there is nothing we can do
$message = sprintf('Could not download the executable <info>%s</info>', $executable);
return Result::error($this, $message);
}
}
// check if target directory exists
if (!is_dir($this->executableTargetDir)) {
mkdir($this->executableTargetDir);
}
// save the executable into the target dir
$path = $this->executableTargetDir . '/' . $executable;
if (substr($os, 0, 3) == 'win') {
// if it is win, add a .exe extension
$path = $this->executableTargetDir . '/' . $executable . '.exe';
}
$result = file_put_contents($path, $data);
if ($result === false) {
$message = sprintf('Could not copy the executable <info>%s</info> to %s', $executable, $path);
return Result::error($this, $message);
}
// set the binary to executable
chmod($path, 0755);
// if everything successful, store the executable path
$this->executablePaths[$executable] = $this->executableTargetDir . '/' . $executable;
// if it is win, add a .exe extension
if (substr($os, 0, 3) == 'win') {
$this->executablePaths[$executable] .= '.exe';
}
$message = sprintf('Executable <info>%s</info> successfully downloaded', $executable);
return Result::success($this, $message);
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function optipng($from, $to)
{
$command = sprintf('optipng -quiet -out "%s" -- "%s"', $to, $from);
if ($from != $to && is_file($to)) {
// earlier versions of optipng do not overwrite the target without a backup
// http://sourceforge.net/p/optipng/bugs/37/
unlink($to);
}
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function jpegtran($from, $to)
{
$command = sprintf('jpegtran -optimize -outfile "%s" "%s"', $to, $from);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function gifsicle($from, $to)
{
$command = sprintf('gifsicle -o "%s" "%s"', $to, $from);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function svgo($from, $to)
{
$command = sprintf('svgo "%s" "%s"', $from, $to);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function pngquant($from, $to)
{
$command = sprintf('pngquant --force --output "%s" "%s"', $to, $from);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function advpng($from, $to)
{
// advpng does not have any output parameters, copy the file and then compress the copy
$command = sprintf('advpng --recompress --quiet "%s"', $to);
$this->fs->copy($from, $to, true);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function pngout($from, $to)
{
$command = sprintf('pngout -y -q "%s" "%s"', $from, $to);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function zopflipng($from, $to)
{
$command = sprintf('zopflipng -y "%s" "%s"', $from, $to);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function pngcrush($from, $to)
{
$command = sprintf('pngcrush -q -ow "%s" "%s"', $from, $to);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function jpegoptim($from, $to)
{
// jpegoptim only takes the destination directory as an argument
$command = sprintf('jpegoptim --quiet -o --dest "%s" "%s"', dirname($to), $from);
return $command;
}
/**
* @param string $from
* @param string $to
*
* @return string
*/
protected function jpegRecompress($from, $to)
{
$command = sprintf('jpeg-recompress --quiet "%s" "%s"', $from, $to);
return $command;
}
/**
* @param string $text
*
* @return string
*/
public static function camelCase($text)
{
// non-alpha and non-numeric characters become spaces
$text = preg_replace('/[^a-z0-9]+/i', ' ', $text);
$text = trim($text);
// uppercase the first character of each word
$text = ucwords($text);
$text = str_replace(" ", "", $text);
$text = lcfirst($text);
return $text;
}
}
<?php
namespace Robo\Task\Assets;
use Robo\Result;
use Robo\Task\BaseTask;
abstract class CssPreprocessor extends BaseTask
{
const FORMAT_NAME = '';
/**
* Default compiler to use.
*
* @var string
*/
protected $compiler;
/**
* Available compilers list
*
* @var string[]
*/
protected $compilers = [];
/**
* Compiler options.
*
* @var array
*/
protected $compilerOptions = [];
/**
* @var array
*/
protected $files = [];
/**
* Constructor. Accepts array of file paths.
*
* @param array $input
*/
public function __construct(array $input)
{
$this->files = $input;
$this->setDefaultCompiler();
}
protected function setDefaultCompiler()
{
if (isset($this->compilers[0])) {
//set first compiler as default
$this->compiler = $this->compilers[0];
}
}
/**
* Sets import directories
* Alias for setImportPaths
* @see CssPreprocessor::setImportPaths
*
* @param array|string $dirs
*
* @return $this
*/
public function importDir($dirs)
{
return $this->setImportPaths($dirs);
}
/**
* Adds import directory
*
* @param string $dir
*
* @return $this
*/
public function addImportPath($dir)
{
if (!isset($this->compilerOptions['importDirs'])) {
$this->compilerOptions['importDirs'] = [];
}
if (!in_array($dir, $this->compilerOptions['importDirs'], true)) {
$this->compilerOptions['importDirs'][] = $dir;
}
return $this;
}
/**
* Sets import directories
*
* @param array|string $dirs
*
* @return $this
*/
public function setImportPaths($dirs)
{
if (!is_array($dirs)) {
$dirs = [$dirs];
}
$this->compilerOptions['importDirs'] = $dirs;
return $this;
}
/**
* @param string $formatterName
*
* @return $this
*/
public function setFormatter($formatterName)
{
$this->compilerOptions['formatter'] = $formatterName;
return $this;
}
/**
* Sets the compiler.
*
* @param string $compiler
* @param array $options
*
* @return $this
*/
public function compiler($compiler, array $options = [])
{
$this->compiler = $compiler;
$this->compilerOptions = array_merge($this->compilerOptions, $options);
return $this;
}
/**
* Compiles file
*
* @param $file
*
* @return bool|mixed
*/
protected function compile($file)
{
if (is_callable($this->compiler)) {
return call_user_func($this->compiler, $file, $this->compilerOptions);
}
if (method_exists($this, $this->compiler)) {
return $this->{$this->compiler}($file);
}
return false;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (!in_array($this->compiler, $this->compilers, true)
&& !is_callable($this->compiler)
) {
$message = sprintf('Invalid ' . static::FORMAT_NAME . ' compiler %s!', $this->compiler);
return Result::error($this, $message);
}
foreach ($this->files as $in => $out) {
if (!file_exists($in)) {
$message = sprintf('File %s not found.', $in);
return Result::error($this, $message);
}
if (file_exists($out) && !is_writable($out)) {
return Result::error($this, 'Destination already exists and cannot be overwritten.');
}
}
foreach ($this->files as $in => $out) {
$css = $this->compile($in);
if ($css instanceof Result) {
return $css;
} elseif (false === $css) {
$message = sprintf(
ucfirst(static::FORMAT_NAME) . ' compilation failed for %s.',
$in
);
return Result::error($this, $message);
}
$dst = $out . '.part';
$write_result = file_put_contents($dst, $css);
if (false === $write_result) {
$message = sprintf('File write failed: %s', $out);
@unlink($dst);
return Result::error($this, $message);
}
// Cannot be cross-volume: should always succeed
@rename($dst, $out);
$this->printTaskSuccess('Wrote CSS to {filename}', ['filename' => $out]);
}
return Result::success($this, 'All ' . static::FORMAT_NAME . ' files compiled.');
}
}
<?php
namespace Robo\Task\Assets;
use Robo\Result;
use Robo\Task\BaseTask;
/**
* Minifies an asset file (CSS or JS).
*
* ``` php
* <?php
* $this->taskMinify('web/assets/theme.css')
* ->run()
* ?>
* ```
* Please install additional packages to use this task:
*
* ```
* composer require patchwork/jsqueeze:^2.0
* composer require natxet/cssmin:^3.0
* ```
*/
class Minify extends BaseTask
{
/**
* @var string[]
*/
protected $types = ['css', 'js'];
/**
* @var string
*/
protected $text;
/**
* @var string
*/
protected $dst;
/**
* @var string
*/
protected $type;
/**
* @var bool[]
*/
protected $squeezeOptions = [
'singleLine' => true,
'keepImportantComments' => true,
'specialVarRx' => false,
];
/**
* Constructor. Accepts asset file path or string source.
*
* @param string $input
*/
public function __construct($input)
{
if (file_exists($input)) {
$this->fromFile($input);
return;
}
$this->fromText($input);
}
/**
* Sets destination. Tries to guess type from it.
*
* @param string $dst
*
* @return $this
*/
public function to($dst)
{
$this->dst = $dst;
if (!empty($this->dst) && empty($this->type)) {
$this->type($this->getExtension($this->dst));
}
return $this;
}
/**
* Sets type with validation.
*
* @param string $type
* Allowed values: "css", "js".
*
* @return $this
*/
public function type($type)
{
$type = strtolower($type);
if (in_array($type, $this->types)) {
$this->type = $type;
}
return $this;
}
/**
* Sets text from string source.
*
* @param string $text
*
* @return $this
*/
protected function fromText($text)
{
$this->text = (string)$text;
unset($this->type);
return $this;
}
/**
* Sets text from asset file path. Tries to guess type and set default destination.
*
* @param string $path
*
* @return $this
*/
protected function fromFile($path)
{
$this->text = file_get_contents($path);
unset($this->type);
$this->type($this->getExtension($path));
if (empty($this->dst) && !empty($this->type)) {
$ext_length = strlen($this->type) + 1;
$this->dst = substr($path, 0, -$ext_length) . '.min.' . $this->type;
}
return $this;
}
/**
* Gets file extension from path.
*
* @param string $path
*
* @return string
*/
protected function getExtension($path)
{
return pathinfo($path, PATHINFO_EXTENSION);
}
/**
* Minifies and returns text.
*
* @return string|bool
*/
protected function getMinifiedText()
{
switch ($this->type) {
case 'css':
if (!class_exists('\CssMin')) {
return Result::errorMissingPackage($this, 'CssMin', 'natxet/cssmin');
}
return \CssMin::minify($this->text);
break;
case 'js':
if (!class_exists('\JSqueeze') && !class_exists('\Patchwork\JSqueeze')) {
return Result::errorMissingPackage($this, 'Patchwork\JSqueeze', 'patchwork/jsqueeze');
}
if (class_exists('\JSqueeze')) {
$jsqueeze = new \JSqueeze();
} else {
$jsqueeze = new \Patchwork\JSqueeze();
}
return $jsqueeze->squeeze(
$this->text,
$this->squeezeOptions['singleLine'],
$this->squeezeOptions['keepImportantComments'],
$this->squeezeOptions['specialVarRx']
);
break;
}
return false;
}
/**
* Single line option for the JS minimisation.
*
* @param bool $singleLine
*
* @return $this
*/
public function singleLine($singleLine)
{
$this->squeezeOptions['singleLine'] = (bool)$singleLine;
return $this;
}
/**
* keepImportantComments option for the JS minimisation.
*
* @param bool $keepImportantComments
*
* @return $this
*/
public function keepImportantComments($keepImportantComments)
{
$this->squeezeOptions['keepImportantComments'] = (bool)$keepImportantComments;
return $this;
}
/**
* Set specialVarRx option for the JS minimisation.
*
* @param bool $specialVarRx
*
* @return $this
*/
public function specialVarRx($specialVarRx)
{
$this->squeezeOptions['specialVarRx'] = (bool)$specialVarRx;
return $this;
}
/**
* @return string
*/
public function __toString()
{
return (string) $this->getMinifiedText();
}
/**
* {@inheritdoc}
*/
public function run()
{
if (empty($this->type)) {
return Result::error($this, 'Unknown asset type.');
}
if (empty($this->dst)) {
return Result::error($this, 'Unknown file destination.');
}
if (file_exists($this->dst) && !is_writable($this->dst)) {
return Result::error($this, 'Destination already exists and cannot be overwritten.');
}
$size_before = strlen($this->text);
$minified = $this->getMinifiedText();
if ($minified instanceof Result) {
return $minified;
} elseif (false === $minified) {
return Result::error($this, 'Minification failed.');
}
$size_after = strlen($minified);
// Minification did not reduce file size, so use original file.
if ($size_after > $size_before) {
$minified = $this->text;
$size_after = $size_before;
}
$dst = $this->dst . '.part';
$write_result = file_put_contents($dst, $minified);
if (false === $write_result) {
@unlink($dst);
return Result::error($this, 'File write failed.');
}
// Cannot be cross-volume; should always succeed.
@rename($dst, $this->dst);
if ($size_before === 0) {
$minified_percent = 0;
} else {
$minified_percent = number_format(100 - ($size_after / $size_before * 100), 1);
}
$this->printTaskSuccess('Wrote {filepath}', ['filepath' => $this->dst]);
$context = [
'bytes' => $this->formatBytes($size_after),
'reduction' => $this->formatBytes(($size_before - $size_after)),
'percentage' => $minified_percent,
];
$this->printTaskSuccess('Wrote {bytes} (reduced by {reduction} / {percentage})', $context);
return Result::success($this, 'Asset minified.');
}
}
<?php
namespace Robo\Task\Assets;
use Robo\Result;
/**
* Compiles less files.
*
* ```php
* <?php
* $this->taskLess([
* 'less/default.less' => 'css/default.css'
* ])
* ->run();
* ?>
* ```
*
* Use one of both less compilers in your project:
*
* ```
* "leafo/lessphp": "~0.5",
* "oyejorge/less.php": "~1.5"
* ```
*
* Specify directory (string or array) for less imports lookup:
*
* ```php
* <?php
* $this->taskLess([
* 'less/default.less' => 'css/default.css'
* ])
* ->importDir('less')
* ->compiler('lessphp')
* ->run();
* ?>
* ```
*
* You can implement additional compilers by extending this task and adding a
* method named after them and overloading the lessCompilers() method to
* inject the name there.
*/
class Less extends CssPreprocessor
{
const FORMAT_NAME = 'less';
/**
* @var string[]
*/
protected $compilers = [
'less', // https://github.com/oyejorge/less.php
'lessphp', //https://github.com/leafo/lessphp
];
/**
* lessphp compiler
* @link https://github.com/leafo/lessphp
*
* @param string $file
*
* @return string
*/
protected function lessphp($file)
{
if (!class_exists('\lessc')) {
return Result::errorMissingPackage($this, 'lessc', 'leafo/lessphp');
}
$lessCode = file_get_contents($file);
$less = new \lessc();
if (isset($this->compilerOptions['importDirs'])) {
$less->setImportDir($this->compilerOptions['importDirs']);
}
return $less->compile($lessCode);
}
/**
* less compiler
* @link https://github.com/oyejorge/less.php
*
* @param string $file
*
* @return string
*/
protected function less($file)
{
if (!class_exists('\Less_Parser')) {
return Result::errorMissingPackage($this, 'Less_Parser', 'oyejorge/less.php');
}
$lessCode = file_get_contents($file);
$parser = new \Less_Parser();
$parser->SetOptions($this->compilerOptions);
if (isset($this->compilerOptions['importDirs'])) {
$importDirs = [];
foreach ($this->compilerOptions['importDirs'] as $dir) {
$importDirs[$dir] = $dir;
}
$parser->SetImportDirs($importDirs);
}
$parser->parse($lessCode);
return $parser->getCss();
}
}
<?php
namespace Robo\Task\Assets;
trait loadTasks
{
/**
* @param string $input
*
* @return \Robo\Task\Assets\Minify|\Robo\Collection\CollectionBuilder
*/
protected function taskMinify($input)
{
return $this->task(Minify::class, $input);
}
/**
* @param string|string[] $input
*
* @return \Robo\Task\Assets\ImageMinify|\Robo\Collection\CollectionBuilder
*/
protected function taskImageMinify($input)
{
return $this->task(ImageMinify::class, $input);
}
/**
* @param array $input
*
* @return \Robo\Task\Assets\Less|\Robo\Collection\CollectionBuilder
*/
protected function taskLess($input)
{
return $this->task(Less::class, $input);
}
/**
* @param array $input
*
* @return \Robo\Task\Assets\Scss|\Robo\Collection\CollectionBuilder
*/
protected function taskScss($input)
{
return $this->task(Scss::class, $input);
}
}
<?php
namespace Robo\Task;
use Robo\Common\InflectionTrait;
use Robo\Contract\InflectionInterface;
use Robo\Common\TaskIO;
use Robo\Contract\TaskInterface;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Contract\VerbosityThresholdInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Robo\Contract\ConfigAwareInterface;
use Psr\Log\LoggerAwareInterface;
use Robo\Contract\OutputAwareInterface;
abstract class BaseTask implements TaskInterface, LoggerAwareInterface, VerbosityThresholdInterface, ConfigAwareInterface, ProgressIndicatorAwareInterface, InflectionInterface
{
use TaskIO; // uses LoggerAwareTrait, VerbosityThresholdTrait and ConfigAwareTrait
use ProgressIndicatorAwareTrait;
use InflectionTrait;
/**
* ConfigAwareInterface uses this to decide where configuration
* items come from. Default is this prefix + class name + key,
* e.g. `task.Remote.Ssh.remoteDir`.
*
* @return string
*/
protected static function configPrefix()
{
return 'task.';
}
/**
* ConfigAwareInterface uses this to decide where configuration
* items come from. Default is this prefix + class name + key,
* e.g. `task.Ssh.remoteDir`.
*
* @return string
*/
protected static function configPostfix()
{
return '.settings';
}
/**
* {@inheritdoc}
*/
public function injectDependencies(InflectionInterface $child)
{
if ($child instanceof LoggerAwareInterface && $this->logger) {
$child->setLogger($this->logger);
}
if ($child instanceof ProgressIndicatorAwareInterface && $this->progressIndicator) {
$child->setProgressIndicator($this->progressIndicator);
}
if ($child instanceof ConfigAwareInterface && $this->getConfig()) {
$child->setConfig($this->getConfig());
}
if ($child instanceof VerbosityThresholdInterface && $this->outputAdapter()) {
$child->setOutputAdapter($this->outputAdapter());
}
}
}
<?php
namespace Robo\Task\Npm;
/**
* Npm Update
*
* ```php
* <?php
* // simple execution
* $this->taskNpmUpdate()->run();
*
* // prefer dist with custom path
* $this->taskNpmUpdate('path/to/my/npm')
* ->noDev()
* ->run();
* ?>
* ```
*/
class Update extends Base
{
/**
* {@inheritdoc}
*/
protected $action = 'update';
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Update Npm packages: {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Npm;
use Robo\Contract\CommandInterface;
/**
* Npm Install
*
* ``` php
* <?php
* // simple execution
* $this->taskNpmInstall()->run();
*
* // prefer dist with custom path
* $this->taskNpmInstall('path/to/my/npm')
* ->noDev()
* ->run();
* ?>
* ```
*/
class Install extends Base implements CommandInterface
{
/**
* {@inheritdoc}
*/
protected $action = 'install';
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Install Npm packages: {arguments}', ['arguments' => $this->arguments]);
return $this->executeCommand($this->getCommand());
}
}
<?php
namespace Robo\Task\Npm;
use Robo\Task\BaseTask;
use Robo\Exception\TaskException;
abstract class Base extends BaseTask
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command = '';
/**
* @var string[]
*/
protected $opts = [];
/**
* @var string
*/
protected $action = '';
/**
* adds `production` option to npm
*
* @return $this
*/
public function noDev()
{
$this->option('production');
return $this;
}
/**
* @param null|string $pathToNpm
*
* @throws \Robo\Exception\TaskException
*/
public function __construct($pathToNpm = null)
{
$this->command = $pathToNpm;
if (!$this->command) {
$this->command = $this->findExecutable('npm');
}
if (!$this->command) {
throw new TaskException(__CLASS__, "Npm executable not found.");
}
}
/**
* @return string
*/
public function getCommand()
{
return "{$this->command} {$this->action}{$this->arguments}";
}
}
<?php
namespace Robo\Task\Npm;
trait loadTasks
{
/**
* @param null|string $pathToNpm
*
* @return \Robo\Task\Npm\Install|\Robo\Collection\CollectionBuilder
*/
protected function taskNpmInstall($pathToNpm = null)
{
return $this->task(Install::class, $pathToNpm);
}
/**
* @param null|string $pathToNpm
*
* @return \Robo\Task\Npm\Update|\Robo\Collection\CollectionBuilder
*/
protected function taskNpmUpdate($pathToNpm = null)
{
return $this->task(Update::class, $pathToNpm);
}
}
<?php
namespace Robo\Task\Base;
use Lurker\ResourceWatcher;
use Robo\Result;
use Robo\Task\BaseTask;
/**
* Runs task when specified file or dir was changed.
* Uses Lurker library.
* Monitor third parameter takes Lurker filesystem events types to watch.
* By default its set to MODIFY event.
*
* ``` php
* <?php
* $this->taskWatch()
* ->monitor(
* 'composer.json',
* function() {
* $this->taskComposerUpdate()->run();
* }
* )->monitor(
* 'src',
* function() {
* $this->taskExec('phpunit')->run();
* },
* \Lurker\Event\FilesystemEvent::ALL
* )->monitor(
* 'migrations',
* function() {
* //do something
* },
* [
* \Lurker\Event\FilesystemEvent::CREATE,
* \Lurker\Event\FilesystemEvent::DELETE
* ]
* )->run();
* ?>
* ```
*
* Pass through the changed file to the callable function
*
* ```
* $this
* ->taskWatch()
* ->monitor(
* 'filename',
* function ($event) {
* $resource = $event->getResource();
* ... do something with (string)$resource ...
* },
* FilesystemEvent::ALL
* )
* ->run();
* ```
*
* The $event parameter is a [standard Symfony file resource object](https://api.symfony.com/3.1/Symfony/Component/Config/Resource/FileResource.html)
*/
class Watch extends BaseTask
{
/**
* @var \Closure
*/
protected $closure;
/**
* @var array
*/
protected $monitor = [];
/**
* @var object
*/
protected $bindTo;
/**
* @param $bindTo
*/
public function __construct($bindTo)
{
$this->bindTo = $bindTo;
}
/**
* @param string|string[] $paths
* @param \Closure $callable
* @param int|int[] $events
*
* @return $this
*/
public function monitor($paths, \Closure $callable, $events = 2)
{
$this->monitor[] = [(array)$paths, $callable, (array)$events];
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
if (!class_exists('Lurker\\ResourceWatcher')) {
return Result::errorMissingPackage($this, 'ResourceWatcher', 'henrikbjorn/lurker');
}
$watcher = new ResourceWatcher();
foreach ($this->monitor as $k => $monitor) {
/** @var \Closure $closure */
$closure = $monitor[1];
$closure->bindTo($this->bindTo);
foreach ($monitor[0] as $i => $dir) {
foreach ($monitor[2] as $j => $event) {
$watcher->track("fs.$k.$i.$j", $dir, $event);
$watcher->addListener("fs.$k.$i.$j", $closure);
}
$this->printTaskInfo('Watching {dir} for changes...', ['dir' => $dir]);
}
}
$watcher->start();
return Result::success($this);
}
}
<?php
namespace Robo\Task\Base;
use Robo\Task\CommandStack;
/**
* Execute commands one by one in stack.
* Stack can be stopped on first fail if you call `stopOnFail()`.
*
* ```php
* <?php
* $this->taskExecStack()
* ->stopOnFail()
* ->exec('mkdir site')
* ->exec('cd site')
* ->run();
*
* ?>
* ```
*/
class ExecStack extends CommandStack
{
}
<?php
namespace Robo\Task\Base;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Result;
use Robo\Task\BaseTask;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process;
/**
* Class ParallelExecTask
*
* ``` php
* <?php
* $this->taskParallelExec()
* ->process('php ~/demos/script.php hey')
* ->process('php ~/demos/script.php hoy')
* ->process('php ~/demos/script.php gou')
* ->run();
* ?>
* ```
*/
class ParallelExec extends BaseTask implements CommandInterface, PrintedInterface
{
use \Robo\Common\CommandReceiver;
/**
* @var Process[]
*/
protected $processes = [];
/**
* @var null|int
*/
protected $timeout = null;
/**
* @var null|int
*/
protected $idleTimeout = null;
/**
* @var null|int
*/
protected $waitInterval = 0;
/**
* @var bool
*/
protected $isPrinted = false;
/**
* {@inheritdoc}
*/
public function getPrinted()
{
return $this->isPrinted;
}
/**
* @param bool $isPrinted
*
* @return $this
*/
public function printed($isPrinted = true)
{
$this->isPrinted = $isPrinted;
return $this;
}
/**
* @param string|\Robo\Contract\CommandInterface $command
*
* @return $this
*/
public function process($command)
{
// TODO: Symfony 4 requires that we supply the working directory.
$this->processes[] = new Process($this->receiveCommand($command), getcwd());
return $this;
}
/**
* Stops process if it runs longer then `$timeout` (seconds).
*
* @param int $timeout
*
* @return $this
*/
public function timeout($timeout)
{
$this->timeout = $timeout;
return $this;
}
/**
* Stops process if it does not output for time longer then `$timeout` (seconds).
*
* @param int $idleTimeout
*
* @return $this
*/
public function idleTimeout($idleTimeout)
{
$this->idleTimeout = $idleTimeout;
return $this;
}
/**
* Parallel processing will wait `$waitInterval` seconds after launching each process and before
* the next one.
*
* @param int $waitInterval
*
* @return $this
*/
public function waitInterval($waitInterval)
{
$this->waitInterval = $waitInterval;
return $this;
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return implode(' && ', $this->processes);
}
/**
* @return int
*/
public function progressIndicatorSteps()
{
return count($this->processes);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->startProgressIndicator();
$running = [];
$queue = $this->processes;
$nextTime = time();
while (true) {
if (($nextTime <= time()) && !empty($queue)) {
$process = array_shift($queue);
$process->setIdleTimeout($this->idleTimeout);
$process->setTimeout($this->timeout);
$process->start();
$this->printTaskInfo($process->getCommandLine());
$running[] = $process;
$nextTime = time() + $this->waitInterval;
}
foreach ($running as $k => $process) {
try {
$process->checkTimeout();
} catch (ProcessTimedOutException $e) {
$this->printTaskWarning("Process timed out for {command}", ['command' => $process->getCommandLine(), '_style' => ['command' => 'fg=white;bg=magenta']]);
}
if (!$process->isRunning()) {
$this->advanceProgressIndicator();
if ($this->isPrinted) {
$this->printTaskInfo("Output for {command}:\n\n{output}", ['command' => $process->getCommandLine(), 'output' => $process->getOutput(), '_style' => ['command' => 'fg=white;bg=magenta']]);
$errorOutput = $process->getErrorOutput();
if ($errorOutput) {
$this->printTaskError(rtrim($errorOutput));
}
}
unset($running[$k]);
}
}
if (empty($running) && empty($queue)) {
break;
}
usleep(1000);
}
$this->stopProgressIndicator();
$errorMessage = '';
$exitCode = 0;
foreach ($this->processes as $p) {
if ($p->getExitCode() === 0) {
continue;
}
$errorMessage .= "'" . $p->getCommandLine() . "' exited with code " . $p->getExitCode() . " \n";
$exitCode = max($exitCode, $p->getExitCode());
}
if (!$errorMessage) {
$this->printTaskSuccess('{process-count} processes finished running', ['process-count' => count($this->processes)]);
}
return new Result($this, $exitCode, $errorMessage, ['time' => $this->getExecutionTime()]);
}
}
<?php
namespace Robo\Task\Base;
trait loadShortcuts
{
/**
* Executes shell command
*
* @param string|\Robo\Contract\CommandInterface $command
*
* @return \Robo\Result
*/
protected function _exec($command)
{
return $this->taskExec($command)->run();
}
}
<?php
namespace Robo\Task\Base;
use Robo\Common\ExecTrait;
use Robo\Contract\CommandInterface;
use Robo\Contract\PrintedInterface;
use Robo\Contract\SimulatedInterface;
use Robo\Task\BaseTask;
use Symfony\Component\Process\Process;
use Robo\Result;
/**
* Executes shell script. Closes it when running in background mode.
*
* ``` php
* <?php
* $this->taskExec('compass')->arg('watch')->run();
* // or use shortcut
* $this->_exec('compass watch');
*
* $this->taskExec('compass watch')->background()->run();
*
* if ($this->taskExec('phpunit .')->run()->wasSuccessful()) {
* $this->say('tests passed');
* }
*
* ?>
* ```
*/
class Exec extends BaseTask implements CommandInterface, PrintedInterface, SimulatedInterface
{
use \Robo\Common\CommandReceiver;
use \Robo\Common\ExecOneCommand;
/**
* @var static[]
*/
protected static $instances = [];
/**
* @var string|\Robo\Contract\CommandInterface
*/
protected $command;
/**
* @param string|\Robo\Contract\CommandInterface $command
*/
public function __construct($command)
{
$this->command = $this->receiveCommand($command);
}
public function __destruct()
{
$this->stop();
}
/**
* Executes command in background mode (asynchronously)
*
* @param bool $arg
*
* @return $this
*/
public function background($arg = true)
{
self::$instances[] = $this;
$this->background = $arg;
return $this;
}
/**
* {@inheritdoc}
*/
protected function getCommandDescription()
{
return $this->getCommand();
}
/**
* {@inheritdoc}
*/
public function getCommand()
{
return trim($this->command . $this->arguments);
}
/**
* {@inheritdoc}
*/
public function simulate($context)
{
$this->printAction($context);
}
public static function stopRunningJobs()
{
foreach (self::$instances as $instance) {
if ($instance) {
unset($instance);
}
}
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->hideProgressIndicator();
// TODO: Symfony 4 requires that we supply the working directory.
$result_data = $this->execute(new Process($this->getCommand(), getcwd()));
return new Result(
$this,
$result_data->getExitCode(),
$result_data->getMessage(),
$result_data->getData()
);
$this->showProgressIndicator();
}
}
if (function_exists('pcntl_signal')) {
pcntl_signal(SIGTERM, ['Robo\Task\Base\Exec', 'stopRunningJobs']);
}
register_shutdown_function(['Robo\Task\Base\Exec', 'stopRunningJobs']);
<?php
namespace Robo\Task\Base;
trait loadTasks
{
/**
* @param string|\Robo\Contract\CommandInterface $command
*
* @return \Robo\Task\Base\Exec|\Robo\Collection\CollectionBuilder
*/
protected function taskExec($command)
{
return $this->task(Exec::class, $command);
}
/**
* @return \Robo\Task\Base\ExecStack|\Robo\Collection\CollectionBuilder
*/
protected function taskExecStack()
{
return $this->task(ExecStack::class);
}
/**
* @return \Robo\Task\Base\ParallelExec|\Robo\Collection\CollectionBuilder
*/
protected function taskParallelExec()
{
return $this->task(ParallelExec::class);
}
/**
* @param \Symfony\Component\Console\Command\Command $command
*
* @return \Robo\Task\Base\SymfonyCommand|\Robo\Collection\CollectionBuilder
*/
protected function taskSymfonyCommand($command)
{
return $this->task(SymfonyCommand::class, $command);
}
/**
* @return \Robo\Task\Base\Watch|\Robo\Collection\CollectionBuilder
*/
protected function taskWatch()
{
return $this->task(Watch::class, $this);
}
}
<?php
namespace Robo\Task\Base;
use Robo\Robo;
use Robo\Result;
use Robo\Task\BaseTask;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
/**
* Executes Symfony Command
*
* ``` php
* <?php
* // Symfony Command
* $this->taskSymfonyCommand(new \Codeception\Command\Run('run'))
* ->arg('suite','acceptance')
* ->opt('debug')
* ->run();
*
* // Artisan Command
* $this->taskSymfonyCommand(new ModelGeneratorCommand())
* ->arg('name', 'User')
* ->run();
* ?>
* ```
*/
class SymfonyCommand extends BaseTask
{
/**
* @var \Symfony\Component\Console\Command\Command
*/
protected $command;
/**
* @var string[]
*/
protected $input;
public function __construct(Command $command)
{
$this->command = $command;
$this->input = [];
}
/**
* @param string $arg
* @param string $value
*
* @return $this
*/
public function arg($arg, $value)
{
$this->input[$arg] = $value;
return $this;
}
public function opt($option, $value = null)
{
$this->input["--$option"] = $value;
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->printTaskInfo('Running command {command}', ['command' => $this->command->getName()]);
return new Result(
$this,
$this->command->run(new ArrayInput($this->input), Robo::output())
);
}
}
<?php
namespace Robo\Task;
use Robo\Contract\WrappedTaskInterface;
use Robo\Exception\TaskException;
use Robo\TaskInfo;
use Robo\Result;
use Robo\Contract\TaskInterface;
use Robo\Contract\SimulatedInterface;
use Robo\Log\RoboLogLevel;
use Robo\Contract\CommandInterface;
class Simulator extends BaseTask implements CommandInterface
{
/**
* @var \Robo\Contract\TaskInterface
*/
protected $task;
/**
* @var array
*/
protected $constructorParameters;
/**
* @var array
*/
protected $stack = [];
/**
* @param \Robo\Contract\TaskInterface $task
* @param array $constructorParameters
*/
public function __construct(TaskInterface $task, $constructorParameters)
{
// TODO: If we ever want to convert the simulated task back into
// an executable task, then we should save the wrapped task.
$this->task = ($task instanceof WrappedTaskInterface) ? $task->original() : $task;
$this->constructorParameters = $constructorParameters;
}
/**
* @param string $function
* @param array $args
*
* @return \Robo\Result|$this
*/
public function __call($function, $args)
{
$this->stack[] = array_merge([$function], $args);
$result = call_user_func_array([$this->task, $function], $args);
return $result == $this->task ? $this : $result;
}
/**
* {@inheritdoc}
*/
public function run()
{
$callchain = '';
foreach ($this->stack as $action) {
$command = array_shift($action);
$parameters = $this->formatParameters($action);
$callchain .= "\n ->$command(<fg=green>$parameters</>)";
}
$context = $this->getTaskContext(
[
'_level' => RoboLogLevel::SIMULATED_ACTION,
'simulated' => TaskInfo::formatTaskName($this->task),
'parameters' => $this->formatParameters($this->constructorParameters),
'_style' => ['simulated' => 'fg=blue;options=bold'],
]
);
// RoboLogLevel::SIMULATED_ACTION
$this->printTaskInfo(
"Simulating {simulated}({parameters})$callchain",
$context
);
$result = null;
if ($this->task instanceof SimulatedInterface) {
$result = $this->task->simulate($context);
}
if (!isset($result)) {
$result = Result::success($this);
}
return $result;
}
/**
* Danger: reach through the simulated wrapper and pull out the command
* to be executed. This is used when using a simulated task with another
* simulated task that runs commands, e.g. the Remote\Ssh task. Using
* a simulated CommandInterface task with a non-simulated task may produce
* unexpected results (e.g. execution!).
*
* @return string
*
* @throws \Robo\Exception\TaskException
*/
public function getCommand()
{
if (!$this->task instanceof CommandInterface) {
throw new TaskException($this->task, 'Simulated task that is not a CommandInterface used as a CommandInterface.');
}
return $this->task->getCommand();
}
/**
* @param array $action
*
* @return string
*/
protected function formatParameters($action)
{
$parameterList = array_map([$this, 'convertParameter'], $action);
return implode(', ', $parameterList);
}
/**
* @param mixed $item
*
* @return string
*/
protected function convertParameter($item)
{
if (is_callable($item)) {
return 'inline_function(...)';
}
if (is_array($item)) {
return $this->shortenParameter(var_export($item, true));
}
if (is_object($item)) {
return '[' . get_class($item) . ' object]';
}
if (is_string($item)) {
return $this->shortenParameter("'$item'");
}
if (is_null($item)) {
return 'null';
}
return $item;
}
/**
* @param string $item
* @param string $shortForm
*
* @return string
*/
protected function shortenParameter($item, $shortForm = '')
{
$maxLength = 80;
$tailLength = 20;
if (strlen($item) < $maxLength) {
return $item;
}
if (!empty($shortForm)) {
return $shortForm;
}
$item = trim($item);
$tail = preg_replace("#.*\n#ms", '', substr($item, -$tailLength));
$head = preg_replace("#\n.*#ms", '', substr($item, 0, $maxLength - (strlen($tail) + 5)));
return "$head ... $tail";
}
}
<?php
namespace Robo\Task\Remote;
use Robo\Contract\CommandInterface;
use Robo\Exception\TaskException;
use Robo\Task\BaseTask;
use Robo\Contract\SimulatedInterface;
/**
* Runs multiple commands on a remote server.
* Per default, commands are combined with &&, unless stopOnFail is false.
*
* ```php
* <?php
*
* $this->taskSshExec('remote.example.com', 'user')
* ->remoteDir('/var/www/html')
* ->exec('ls -la')
* ->exec('chmod g+x logs')
* ->run();
*
* ```
*
* You can even exec other tasks (which implement CommandInterface):
*
* ```php
* $gitTask = $this->taskGitStack()
* ->checkout('master')
* ->pull();
*
* $this->taskSshExec('remote.example.com')
* ->remoteDir('/var/www/html/site')
* ->exec($gitTask)
* ->run();
* ```
*
* You can configure the remote directory for all future calls:
*
* ```php
* \Robo\Task\Remote\Ssh::configure('remoteDir', '/some-dir');
* ```
*/
class Ssh extends BaseTask implements CommandInterface, SimulatedInterface
{
use \Robo\Common\CommandReceiver;
use \Robo\Common\ExecOneCommand;
/**
* @var null|string
*/
protected $hostname;
/**
* @var null|string
*/
protected $user;
/**
* @var bool
*/
protected $stopOnFail = true;
/**
* @var array
*/
protected $exec = [];
/**
* Changes to the given directory before running commands.
*
* @var string
*/
protected $remoteDir;
/**
* @param null|string $hostname
* @param null|string $user
*/
public function __construct($hostname = null, $user = null)
{
$this->hostname = $hostname;
$this->user = $user;
}
/**
* @param string $hostname
*
* @return $this
*/
public function hostname($hostname)
{
$this->hostname = $hostname;
return $this;
}
/**
* @param string $user
*
* @return $this
*/
public function user($user)
{
$this->user = $user;
return $this;
}
/**
* Whether or not to chain commands together with && and stop the chain if one command fails.
*
* @param bool $stopOnFail
*
* @return $this
*/
public function stopOnFail($stopOnFail = true)
{
$this->stopOnFail = $stopOnFail;
return $this;
}
/**
* Changes to the given directory before running commands.
*
* @param string $remoteDir
*
* @return $this
*/
public function remoteDir($remoteDir)
{
$this->remoteDir = $remoteDir;
return $this;
}
/**
* @param string $filename
*
* @return $this
*/
public function identityFile($filename)
{
$this->option('-i', $filename);
return $this;
}
/**
* @param int $port
*
* @return $this
*/
public function port($port)
{
$this->option('-p', $port);
return $this;
}
/**
* @return $this
*/
public function forcePseudoTty()
{
$this->option('-t');
return $this;
}
/**
* @return $this
*/
public function quiet()
{
$this->option('-q');
return $this;
}
/**
* @return $this
*/
public function verbose()
{
$this->option('-v');
return $this;
}
/**
* @param string|string[]|CommandInterface $command
*
* @return $this
*/
public function exec($command)
{
if (is_array($command)) {
$command = implode(' ', array_filter($command));
}
$this->exec[] = $command;
return $this;
}
/**
* Returns command that can be executed.
* This method is used to pass generated command from one task to another.
*
* @return string
*/
public function getCommand()
{
$commands = [];
foreach ($this->exec as $command) {
$commands[] = $this->receiveCommand($command);
}
$remoteDir = $this->remoteDir ? $this->remoteDir : $this->getConfigValue('remoteDir');
if (!empty($remoteDir)) {
array_unshift($commands, sprintf('cd "%s"', $remoteDir));
}
$command = implode($this->stopOnFail ? ' && ' : ' ; ', $commands);
return $this->sshCommand($command);
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->validateParameters();
$command = $this->getCommand();
return $this->executeCommand($command);
}
/**
* {@inheritdoc}
*/
public function simulate($context)
{
$command = $this->getCommand();
$this->printTaskInfo("Running {command}", ['command' => $command] + $context);
}
protected function validateParameters()
{
if (empty($this->hostname)) {
throw new TaskException($this, 'Please set a hostname');
}
if (empty($this->exec)) {
throw new TaskException($this, 'Please add at least one command');
}
}
/**
* Returns an ssh command string running $command on the remote.
*
* @param string|CommandInterface $command
*
* @return string
*/
protected function sshCommand($command)
{
$command = $this->receiveCommand($command);
$sshOptions = $this->arguments;
$hostSpec = $this->hostname;
if ($this->user) {
$hostSpec = $this->user . '@' . $hostSpec;
}
return "ssh{$sshOptions} {$hostSpec} '{$command}'";
}
}
<?php
namespace Robo\Task\Remote;
trait loadTasks
{
/**
* @return \Robo\Task\Remote\Rsync|\Robo\Collection\CollectionBuilder
*/
protected function taskRsync()
{
return $this->task(Rsync::class);
}
/**
* @param null|string $hostname
* @param null|string $user
*
* @return \Robo\Task\Remote\Ssh|\Robo\Collection\CollectionBuilder
*/
protected function taskSshExec($hostname = null, $user = null)
{
return $this->task(Ssh::class, $hostname, $user);
}
}
<?php
namespace Robo\Task\Remote;
use Robo\Contract\CommandInterface;
use Robo\Task\BaseTask;
use Robo\Exception\TaskException;
/**
* Executes rsync in a flexible manner.
*
* ``` php
* $this->taskRsync()
* ->fromPath('src/')
* ->toHost('localhost')
* ->toUser('dev')
* ->toPath('/var/www/html/app/')
* ->remoteShell('ssh -i public_key')
* ->recursive()
* ->excludeVcs()
* ->checksum()
* ->wholeFile()
* ->verbose()
* ->progress()
* ->humanReadable()
* ->stats()
* ->run();
* ```
*
* You could also clone the task and do a dry-run first:
*
* ``` php
* $rsync = $this->taskRsync()
* ->fromPath('src/')
* ->toPath('example.com:/var/www/html/app/')
* ->archive()
* ->excludeVcs()
* ->progress()
* ->stats();
*
* $dryRun = clone $rsync;
* $dryRun->dryRun()->run();
* if ('y' === $this->ask('Do you want to run (y/n)')) {
* $rsync->run();
* }
* ```
*/
class Rsync extends BaseTask implements CommandInterface
{
use \Robo\Common\ExecOneCommand;
/**
* @var string
*/
protected $command;
/**
* @var string
*/
protected $fromUser;
/**
* @var string
*/
protected $fromHost;
/**
* @var string
*/
protected $fromPath;
/**
* @var string
*/
protected $toUser;
/**
* @var string
*/
protected $toHost;
/**
* @var string
*/
protected $toPath;
/**
* @return static
*/
public static function init()
{
return new static();
}
public function __construct()
{
$this->command = 'rsync';
}
/**
* This can either be a full rsync path spec (user@host:path) or just a path.
* In case of the former do not specify host and user.
*
* @param string|array $path
*
* @return $this
*/
public function fromPath($path)
{
$this->fromPath = $path;
return $this;
}
/**
* This can either be a full rsync path spec (user@host:path) or just a path.
* In case of the former do not specify host and user.
*
* @param string $path
*
* @return $this
*/
public function toPath($path)
{
$this->toPath = $path;
return $this;
}
/**
* @param string $fromUser
*
* @return $this
*/
public function fromUser($fromUser)
{
$this->fromUser = $fromUser;
return $this;
}
/**
* @param string $fromHost
*
* @return $this
*/
public function fromHost($fromHost)
{
$this->fromHost = $fromHost;
return $this;
}
/**
* @param string $toUser
*
* @return $this
*/
public function toUser($toUser)
{
$this->toUser = $toUser;
return $this;
}
/**
* @param string $toHost
*
* @return $this
*/
public function toHost($toHost)
{
$this->toHost = $toHost;
return $this;
}
/**
* @return $this
*/
public function progress()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function stats()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function recursive()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function verbose()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function checksum()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function archive()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function compress()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function owner()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function group()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function times()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @return $this
*/
public function delete()
{
$this->option(__FUNCTION__);
return $this;
}
/**
* @param int $seconds
*
* @return $this
*/
public function timeout($seconds)
{
$this->option(__FUNCTION__, $seconds);
return $this;
}
/**
* @return $this
*/
public function humanReadable()
{
$this->option('human-readable');
return $this;
}
/**
* @return $this
*/
public function wholeFile()
{
$this->option('whole-file');
return $this;
}
/**
* @return $this
*/
public function dryRun()
{
$this->option('dry-run');
return $this;
}
/**
* @return $this
*/
public function itemizeChanges()
{
$this->option('itemize-changes');
return $this;
}
/**
* Excludes .git, .svn and .hg items at any depth.
*
* @return $this
*/
public function excludeVcs()
{
return $this->exclude([
'.git',
'.svn',
'.hg',
]);
}
/**
* @param array|string $pattern
*
* @return $this
*/
public function exclude($pattern)
{
return $this->optionList(__FUNCTION__, $pattern);
}
/**
* @param string $file
*
* @return $this
*
* @throws \Robo\Exception\TaskException
*/
public function excludeFrom($file)
{
if (!is_readable($file)) {
throw new TaskException($this, "Exclude file $file is not readable");
}
return $this->option('exclude-from', $file);
}
/**
* @param array|string $pattern
*
* @return $this
*/
public function includeFilter($pattern)
{
return $this->optionList('include', $pattern);
}
/**
* @param array|string $pattern
*
* @return $this
*/
public function filter($pattern)
{
return $this->optionList(__FUNCTION__, $pattern);
}
/**
* @param string $file
*
* @return $this
*
* @throws \Robo\Exception\TaskException
*/
public function filesFrom($file)
{
if (!is_readable($file)) {
throw new TaskException($this, "Files-from file $file is not readable");
}
return $this->option('files-from', $file);
}
/**
* @param string $command
*
* @return $this
*/
public function remoteShell($command)
{
$this->option('rsh', "$command");
return $this;
}
/**
* {@inheritdoc}
*/
public function run()
{
$command = $this->getCommand();
return $this->executeCommand($command);
}
/**
* Returns command that can be executed.
* This method is used to pass generated command from one task to another.
*
* @return string
*/
public function getCommand()
{
foreach ((array)$this->fromPath as $from) {
$this->option(null, $this->getFromPathSpec($from));
}
$this->option(null, $this->getToPathSpec());
return $this->command . $this->arguments;
}
/**
* @param string $from
*
* @return string
*/
protected function getFromPathSpec($from)
{
return $this->getPathSpec($this->fromHost, $this->fromUser, $from);
}
/**
* @return string
*/
protected function getToPathSpec()
{
return $this->getPathSpec($this->toHost, $this->toUser, $this->toPath);
}
/**
* @param string $host
* @param string $user
* @param string $path
*
* @return string
*/
protected function getPathSpec($host, $user, $path)
{
$spec = isset($path) ? $path : '';
if (!empty($host)) {
$spec = "{$host}:{$spec}";
}
if (!empty($user)) {
$spec = "{$user}@{$spec}";
}
return $spec;
}
}
<?php
namespace Robo\ClassDiscovery;
/**
* Class AbstractClassDiscovery
*
* @package Robo\ClassDiscovery
*/
abstract class AbstractClassDiscovery implements ClassDiscoveryInterface
{
/**
* @var string
*/
protected $searchPattern = '*.php';
/**
* {@inheritdoc}
*/
public function setSearchPattern($searchPattern)
{
$this->searchPattern = $searchPattern;
return $this;
}
}
<?php
namespace Robo\ClassDiscovery;
/**
* Interface ClassDiscoveryInterface
*
* @package Robo\Plugin\ClassDiscovery
*/
interface ClassDiscoveryInterface
{
/**
* @param string $searchPattern
*
* @return $this
*/
public function setSearchPattern($searchPattern);
/**
* @return string[]
*/
public function getClasses();
/**
* @param string $class
*
* @return string|null
*/
public function getFile($class);
}
<?php
namespace Robo\ClassDiscovery;
use Symfony\Component\Finder\Finder;
use Composer\Autoload\ClassLoader;
/**
* Class RelativeNamespaceDiscovery
*
* @package Robo\Plugin\ClassDiscovery
*/
class RelativeNamespaceDiscovery extends AbstractClassDiscovery
{
/**
* @var \Composer\Autoload\ClassLoader
*/
protected $classLoader;
/**
* @var string
*/
protected $relativeNamespace = '';
/**
* RelativeNamespaceDiscovery constructor.
*
* @param \Composer\Autoload\ClassLoader $classLoader
*/
public function __construct(ClassLoader $classLoader)
{
$this->classLoader = $classLoader;
}
/**
* @param string $relativeNamespace
*
* @return $this
*/
public function setRelativeNamespace($relativeNamespace)
{
$this->relativeNamespace = $relativeNamespace;
return $this;
}
/**
* {@inheritDoc}
*/
public function getClasses()
{
$classes = [];
$relativePath = $this->convertNamespaceToPath($this->relativeNamespace);
foreach ($this->classLoader->getPrefixesPsr4() as $baseNamespace => $directories) {
$directories = array_filter(array_map(function ($directory) use ($relativePath) {
return $directory . $relativePath;
}, $directories), 'is_dir');
if ($directories) {
foreach ($this->search($directories, $this->searchPattern) as $file) {
$relativePathName = $file->getRelativePathname();
$classes[] = $baseNamespace . $this->convertPathToNamespace($relativePath . '/' . $relativePathName);
}
}
}
return $classes;
}
/**
* {@inheritdoc}
*/
public function getFile($class)
{
return $this->classLoader->findFile($class);
}
/**
* @param string|array $directories
* @param string $pattern
*
* @return \Symfony\Component\Finder\Finder
*/
protected function search($directories, $pattern)
{
$finder = new Finder();
$finder->files()
->name($pattern)
->in($directories);
return $finder;
}
/**
* @param string $path
*
* @return string
*/
protected function convertPathToNamespace($path)
{
return str_replace(['/', '.php'], ['\\', ''], trim($path, '/'));
}
/**
* @param string $namespace
*
* @return string
*/
public function convertNamespaceToPath($namespace)
{
return '/' . str_replace("\\", '/', trim($namespace, '\\'));
}
}
<?php
namespace Robo;
use Consolidation\AnnotatedCommand\ExitCodeInterface;
use Consolidation\AnnotatedCommand\OutputDataInterface;
use Robo\State\Data;
class ResultData extends Data implements ExitCodeInterface, OutputDataInterface
{
/**
* @var int
*/
protected $exitCode;
const EXITCODE_OK = 0;
const EXITCODE_ERROR = 1;
/** Symfony Console handles these conditions; Robo returns the status
code selected by Symfony. These are here for documentation purposes. */
const EXITCODE_MISSING_OPTIONS = 2;
const EXITCODE_COMMAND_NOT_FOUND = 127;
/** The command was aborted because the user chose to cancel it at some prompt.
This exit code is arbitrarily the same as EX_TEMPFAIL in sysexits.h, although
note that shell error codes are distinct from C exit codes, so this alignment
not particularly meaningful. */
const EXITCODE_USER_CANCEL = 75;
/**
* @param int $exitCode
* @param string $message
* @param array $data
*/
public function __construct($exitCode = self::EXITCODE_OK, $message = '', $data = [])
{
$this->exitCode = $exitCode;
parent::__construct($message, $data);
}
/**
* @param string $message
* @param array $data
*
* @return static
*/
public static function message($message, $data = [])
{
return new self(self::EXITCODE_OK, $message, $data);
}
/**
* @param string $message
* @param array $data
*
* @return static
*/
public static function cancelled($message = '', $data = [])
{
return new ResultData(self::EXITCODE_USER_CANCEL, $message, $data);
}
/**
* @return int
*/
public function getExitCode()
{
return $this->exitCode;
}
/**
* @return null|string
*/
public function getOutputData()
{
if (!empty($this->message) && !isset($this['already-printed']) && isset($this['provide-outputdata'])) {
return $this->message;
}
}
/**
* Indicate that the message in this data has already been displayed.
*/
public function alreadyPrinted()
{
$this['already-printed'] = true;
}
/**
* Opt-in to providing the result message as the output data
*/
public function provideOutputdata()
{
$this['provide-outputdata'] = true;
}
/**
* @return bool
*/
public function wasSuccessful()
{
return $this->exitCode === self::EXITCODE_OK;
}
/**
* @return bool
*/
public function wasCancelled()
{
return $this->exitCode == self::EXITCODE_USER_CANCEL;
}
}
<?php
namespace Robo;
trait LoadAllTasks
{
use TaskAccessor;
use Collection\loadTasks;
// standard tasks
use Task\Base\loadTasks;
use Task\Development\loadTasks;
use Task\Filesystem\loadTasks;
use Task\File\loadTasks;
use Task\Archive\loadTasks;
use Task\Vcs\loadTasks;
// package managers
use Task\Composer\loadTasks;
use Task\Bower\loadTasks;
use Task\Npm\loadTasks;
// assets
use Task\Assets\loadTasks;
// 3rd-party tools
use Task\Remote\loadTasks;
use Task\Testing\loadTasks;
use Task\ApiGen\loadTasks;
use Task\Docker\loadTasks;
// task runners
use Task\Gulp\loadTasks;
// shortcuts
use Task\Base\loadShortcuts;
use Task\Filesystem\loadShortcuts;
use Task\Vcs\loadShortcuts;
}
<?php
namespace Robo\Log;
use Robo\Result;
use Robo\Contract\PrintedInterface;
use Robo\Contract\ProgressIndicatorAwareInterface;
use Robo\Contract\VerbosityThresholdInterface;
use Robo\Common\ProgressIndicatorAwareTrait;
use Psr\Log\LogLevel;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Consolidation\Log\ConsoleLogLevel;
/**
* Log the creation of Result objects.
*/
class ResultPrinter implements LoggerAwareInterface, ProgressIndicatorAwareInterface
{
use LoggerAwareTrait;
use ProgressIndicatorAwareTrait;
/**
* Log the result of a Robo task.
*
* Returns 'true' if the message is printed, or false if it isn't.
*
* @param \Robo\Result $result
*
* @return null|bool
*/
public function printResult(Result $result)
{
$task = $result->getTask();
if ($task instanceof VerbosityThresholdInterface && !$task->verbosityMeetsThreshold()) {
return;
}
if (!$result->wasSuccessful()) {
return $this->printError($result);
} else {
return $this->printSuccess($result);
}
}
/**
* Log that we are about to abort due to an error being encountered
* in 'stop on fail' mode.
*
* @param \Robo\Result $result
*/
public function printStopOnFail($result)
{
$this->printMessage(LogLevel::NOTICE, 'Stopping on fail. Exiting....');
$this->printMessage(LogLevel::ERROR, 'Exit Code: {code}', ['code' => $result->getExitCode()]);
}
/**
* Log the result of a Robo task that returned an error.
*
* @param \Robo\Result $result
*
* @return bool
*/
protected function printError(Result $result)
{
$task = $result->getTask();
$context = $result->getContext() + ['timer-label' => 'Time', '_style' => []];
$context['_style']['message'] = '';
$printOutput = true;
if ($task instanceof PrintedInterface) {
$printOutput = !$task->getPrinted();
}
if ($printOutput) {
$this->printMessage(LogLevel::ERROR, "{message}", $context);
}
$this->printMessage(LogLevel::ERROR, 'Exit code {code}', $context);
return true;
}
/**
* Log the result of a Robo task that was successful.
*
* @param \Robo\Result $result
*
* @return bool
*/
protected function printSuccess(Result $result)
{
$task = $result->getTask();
$context = $result->getContext() + ['timer-label' => 'in'];
$time = $result->getExecutionTime();
if ($time) {
$this->printMessage(ConsoleLogLevel::SUCCESS, 'Done', $context);
}
return false;
}
/**
* @param string $level
* @param string $message
* @param array $context
*/
protected function printMessage($level, $message, $context = [])
{
$inProgress = $this->hideProgressIndicator();
$this->logger->log($level, $message, $context);
if ($inProgress) {
$this->restoreProgressIndicator($inProgress);
}
}
}
<?php
namespace Robo\Log;
class RoboLogLevel extends \Consolidation\Log\ConsoleLogLevel
{
/**
* Command did something in simulated mode.
* Displayed at VERBOSITY_NORMAL.
*/
const SIMULATED_ACTION = 'simulated';
}
<?php
namespace Robo\Log;
use Robo\Common\TimeKeeper;
use Consolidation\Log\LogOutputStyler;
/**
* Robo Log Styler.
*/
class RoboLogStyle extends LogOutputStyler
{
const TASK_STYLE_SIMULATED = 'options=reverse;bold';
/**
* RoboLogStyle constructor.
*
* @param array $labelStyles
* @param array $messageStyles
*/
public function __construct($labelStyles = [], $messageStyles = [])
{
parent::__construct($labelStyles, $messageStyles);
$this->labelStyles += [
RoboLogLevel::SIMULATED_ACTION => self::TASK_STYLE_SIMULATED,
];
$this->messageStyles += [
RoboLogLevel::SIMULATED_ACTION => '',
];
}
/**
* Log style customization for Robo: replace the log level with
* the task name.
*
* @param string $level
* @param string $message
* @param array $context
*
* @return string
*/
protected function formatMessageByLevel($level, $message, $context)
{
$label = $level;
if (array_key_exists('name', $context)) {
$label = $context['name'];
}
return $this->formatMessage($label, $message, $context, $this->labelStyles[$level], $this->messageStyles[$level]);
}
/**
* Log style customization for Robo: add the time indicator to the
* end of the log message if it exists in the context.
*
* @param string $label
* @param string $message
* @param array $context
* @param string $taskNameStyle
* @param string $messageStyle
*
* @return string
*/
protected function formatMessage($label, $message, $context, $taskNameStyle, $messageStyle = '')
{
$message = parent::formatMessage($label, $message, $context, $taskNameStyle, $messageStyle);
if (array_key_exists('time', $context) && !empty($context['time']) && array_key_exists('timer-label', $context)) {
$duration = TimeKeeper::formatDuration($context['time']);
$message .= ' ' . $context['timer-label'] . ' ' . $this->wrapFormatString($duration, 'fg=yellow');
}
return $message;
}
}
<?php
namespace Robo\Log;
use Consolidation\Log\Logger;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Robo's default logger
*/
class RoboLogger extends Logger
{
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*/
public function __construct(OutputInterface $output)
{
// In Robo, we use log level 'notice' for messages that appear all
// the time, and 'info' for messages that appear only during verbose
// output. We have no 'very verbose' (-vv) level. 'Debug' is -vvv, as usual.
$roboVerbosityOverrides = [
RoboLogLevel::SIMULATED_ACTION => OutputInterface::VERBOSITY_NORMAL, // Default is "verbose"
LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL, // Default is "verbose"
LogLevel::INFO => OutputInterface::VERBOSITY_VERBOSE, // Default is "very verbose"
];
parent::__construct($output, $roboVerbosityOverrides);
}
}
<?php
namespace Robo\Symfony;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\CommandProcessor;
use Consolidation\AnnotatedCommand\ParameterInjector;
use Symfony\Component\Console\Style\SymfonyStyle;
class SymfonyStyleInjector implements ParameterInjector
{
public function get(CommandData $commandData, $interfaceName)
{
return new SymfonyStyle($commandData->input(), $commandData->output());
}
}
<?php
namespace Robo\Exception;
/**
* By default, rollbacks and completions tasks or callbacks continue even if
* errors occur. If you would like to explicitly cancel or abort the rollback or
* completion, you may throw this exception to abort the subsequent tasks in the
* rollback or completion task list.
*/
class AbortTasksException extends \Exception
{
}
<?php
namespace Robo\Exception;
class TaskException extends \Exception
{
/**
* TaskException constructor.
*
* @param string|object $class
* @param string $message
*/
public function __construct($class, $message)
{
if (is_object($class)) {
$class = get_class($class);
}
parent::__construct(" in task $class \n\n $message");
}
}
<?php
namespace Robo\Exception;
class TaskExitException extends \Exception
{
/**
* TaskExitException constructor.
*
* @param string|object $class
* @param string $message
* @param int $status
*/
public function __construct($class, $message, $status)
{
if (is_object($class)) {
$class = get_class($class);
}
parent::__construct(" in task $class \n\n $message", $status);
}
}
<?php
namespace Robo;
use Composer\Autoload\ClassLoader;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\StringInput;
use Robo\Contract\BuilderAwareInterface;
use Robo\Collection\CollectionBuilder;
use Robo\Common\IO;
use Robo\Exception\TaskExitException;
use League\Container\ContainerAwareInterface;
use League\Container\ContainerAwareTrait;
use Consolidation\Config\Util\EnvConfig;
class Runner implements ContainerAwareInterface
{
use IO;
use ContainerAwareTrait;
const ROBOCLASS = 'RoboFile';
const ROBOFILE = 'RoboFile.php';
/**
* @var string
*/
protected $roboClass;
/**
* @var string
*/
protected $roboFile;
/**
* Working dir of Robo.
*
* @var string
*/
protected $dir;
/**
* @var string[]
*/
protected $errorConditions = [];
/**
* GitHub Repo for SelfUpdate.
*
* @var string
*/
protected $selfUpdateRepository = null;
/**
* Filename to load configuration from (set to 'robo.yml' for RoboFiles).
*
* @var string
*/
protected $configFilename = 'conf.yml';
/**
* @var string prefix for environment variable configuration overrides
*/
protected $envConfigPrefix = false;
/**
* @var null|\Composer\Autoload\ClassLoader
*/
protected $classLoader = null;
/**
* @var string
*/
protected $relativePluginNamespace;
/**
* Class Constructor
*
* @param null|string $roboClass
* @param null|string $roboFile
*/
public function __construct($roboClass = null, $roboFile = null)
{
// set the const as class properties to allow overwriting in child classes
$this->roboClass = $roboClass ? $roboClass : self::ROBOCLASS ;
$this->roboFile = $roboFile ? $roboFile : self::ROBOFILE;
$this->dir = getcwd();
}
/**
* @param string $msg
* @param string $errorType
*/
protected function errorCondition($msg, $errorType)
{
$this->errorConditions[$msg] = $errorType;
}
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @return bool
*/
protected function loadRoboFile($output)
{
// If we have not been provided an output object, make a temporary one.
if (!$output) {
$output = new \Symfony\Component\Console\Output\ConsoleOutput();
}
// If $this->roboClass is a single class that has not already
// been loaded, then we will try to obtain it from $this->roboFile.
// If $this->roboClass is an array, we presume all classes requested
// are available via the autoloader.
if (is_array($this->roboClass) || class_exists($this->roboClass)) {
return true;
}
if (!file_exists($this->dir)) {
$this->errorCondition("Path `{$this->dir}` is invalid; please provide a valid absolute path to the Robofile to load.", 'red');
return false;
}
$realDir = realpath($this->dir);
$roboFilePath = $realDir . DIRECTORY_SEPARATOR . $this->roboFile;
if (!file_exists($roboFilePath)) {
$requestedRoboFilePath = $this->dir . DIRECTORY_SEPARATOR . $this->roboFile;
$this->errorCondition("Requested RoboFile `$requestedRoboFilePath` is invalid, please provide valid absolute path to load Robofile.", 'red');
return false;
}
require_once $roboFilePath;
if (!class_exists($this->roboClass)) {
$this->errorCondition("Class {$this->roboClass} was not loaded.", 'red');
return false;
}
return true;
}
/**
* @param array $argv
* @param null|string $appName
* @param null|string $appVersion
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
*
* @return int
*/
public function execute($argv, $appName = null, $appVersion = null, $output = null)
{
$argv = $this->shebang($argv);
$argv = $this->processRoboOptions($argv);
$app = null;
if ($appName && $appVersion) {
$app = Robo::createDefaultApplication($appName, $appVersion);
}
$commandFiles = $this->getRoboFileCommands($output);
return $this->run($argv, $output, $app, $commandFiles, $this->classLoader);
}
/**
* Get a list of locations where config files may be loaded
*
* @param string $userConfig
*
* @return string[]
*/
protected function getConfigFilePaths($userConfig)
{
$roboAppConfig = dirname(__DIR__) . '/' . basename($userConfig);
$configFiles = [$userConfig, $roboAppConfig];
if (dirname($userConfig) != '.') {
array_unshift($configFiles, basename($userConfig));
}
return $configFiles;
}
/**
* @param null|\Symfony\Component\Console\Input\InputInterface $input
* @param null|\Symfony\Component\Console\Output\OutputInterface $output
* @param null|\Robo\Application $app
* @param array[] $commandFiles
* @param null|ClassLoader $classLoader
*
* @return int
*/
public function run($input = null, $output = null, $app = null, $commandFiles = [], $classLoader = null)
{
// Create default input and output objects if they were not provided
if (!$input) {
$input = new StringInput('');
}
if (is_array($input)) {
$input = new ArgvInput($input);
}
if (!$output) {
$output = new \Symfony\Component\Console\Output\ConsoleOutput();
}
$this->setInput($input);
$this->setOutput($output);
// If we were not provided a container, then create one
if (!$this->getContainer()) {
$configFiles = $this->getConfigFilePaths($this->configFilename);
$config = Robo::createConfiguration($configFiles);
if ($this->envConfigPrefix) {
$envConfig = new EnvConfig($this->envConfigPrefix);
$config->addContext('env', $envConfig);
}
$container = Robo::createDefaultContainer($input, $output, $app, $config, $classLoader);
$this->setContainer($container);
// Automatically register a shutdown function and
// an error handler when we provide the container.
$this->installRoboHandlers();
}
if (!$app) {
$app = Robo::application();
}
if ($app instanceof \Robo\Application) {
$app->addSelfUpdateCommand($this->getSelfUpdateRepository());
if (!isset($commandFiles)) {
$this->errorCondition("Robo is not initialized here. Please run `robo init` to create a new RoboFile.", 'yellow');
$app->addInitRoboFileCommand($this->roboFile, $this->roboClass);
$commandFiles = [];
}
}
if (!empty($this->relativePluginNamespace)) {
$commandClasses = $this->discoverCommandClasses($this->relativePluginNamespace);
$commandFiles = array_merge((array)$commandFiles, $commandClasses);
}
$this->registerCommandClasses($app, $commandFiles);
try {
$statusCode = $app->run($input, $output);
} catch (TaskExitException $e) {
$statusCode = $e->getCode() ?: 1;
}
// If there were any error conditions in bootstrapping Robo,
// print them only if the requested command did not complete
// successfully.
if ($statusCode) {
foreach ($this->errorConditions as $msg => $color) {
$this->yell($msg, 40, $color);
}
}
return $statusCode;
}
/**
* @param \Symfony\Component\Console\Output\OutputInterface $output
*
* @return null|string
*/
protected function getRoboFileCommands($output)
{
if (!$this->loadRoboFile($output)) {
return;
}
return $this->roboClass;
}
/**
* @param \Robo\Application $app
* @param array $commandClasses
*/
public function registerCommandClasses($app, $commandClasses)
{
foreach ((array)$commandClasses as $commandClass) {
$this->registerCommandClass($app, $commandClass);
}
}
/**
* @param string $relativeNamespace
*
* @return string[]
*/
protected function discoverCommandClasses($relativeNamespace)
{
/** @var \Robo\ClassDiscovery\RelativeNamespaceDiscovery $discovery */
$discovery = Robo::service('relativeNamespaceDiscovery');
$discovery->setRelativeNamespace($relativeNamespace . '\Commands')
->setSearchPattern('/.*Commands?\.php$/');
return $discovery->getClasses();
}
/**
* @param \Robo\Application $app
* @param string|BuilderAwareInterface|ContainerAwareInterface $commandClass
*
* @return null|object
*/
public function registerCommandClass($app, $commandClass)
{
$container = Robo::getContainer();
$roboCommandFileInstance = $this->instantiateCommandClass($commandClass);
if (!$roboCommandFileInstance) {
return;
}
// Register commands for all of the public methods in the RoboFile.
$commandFactory = $container->get('commandFactory');
$commandList = $commandFactory->createCommandsFromClass($roboCommandFileInstance);
foreach ($commandList as $command) {
$app->add($command);
}
return $roboCommandFileInstance;
}
/**
* @param string|\Robo\Contract\BuilderAwareInterface|\League\Container\ContainerAwareInterface $commandClass
*
* @return null|object
*/
protected function instantiateCommandClass($commandClass)
{
$container = Robo::getContainer();
// Register the RoboFile with the container and then immediately
// fetch it; this ensures that all of the inflectors will run.
// If the command class is already an instantiated object, then
// just use it exactly as it was provided to us.
if (is_string($commandClass)) {
if (!class_exists($commandClass)) {
return;
}
$reflectionClass = new \ReflectionClass($commandClass);
if ($reflectionClass->isAbstract()) {
return;
}
$commandFileName = "{$commandClass}Commands";
$container->share($commandFileName, $commandClass);
$commandClass = $container->get($commandFileName);
}
// If the command class is a Builder Aware Interface, then
// ensure that it has a builder. Every command class needs
// its own collection builder, as they have references to each other.
if ($commandClass instanceof BuilderAwareInterface) {
$builder = CollectionBuilder::create($container, $commandClass);
$commandClass->setBuilder($builder);
}
if ($commandClass instanceof ContainerAwareInterface) {
$commandClass->setContainer($container);
}
return $commandClass;
}
public function installRoboHandlers()
{
register_shutdown_function(array($this, 'shutdown'));
set_error_handler(array($this, 'handleError'));
}
/**
* Process a shebang script, if one was used to launch this Runner.
*
* @param array $args
*
* @return array $args
* With shebang script removed.
*/
protected function shebang($args)
{
// Option 1: Shebang line names Robo, but includes no parameters.
// #!/bin/env robo
// The robo class may contain multiple commands; the user may
// select which one to run, or even get a list of commands or
// run 'help' on any of the available commands as usual.
if ((count($args) > 1) && $this->isShebangFile($args[1])) {
return array_merge([$args[0]], array_slice($args, 2));
}
// Option 2: Shebang line stipulates which command to run.
// #!/bin/env robo mycommand
// The robo class must contain a public method named 'mycommand'.
// This command will be executed every time. Arguments and options
// may be provided on the commandline as usual.
if ((count($args) > 2) && $this->isShebangFile($args[2])) {
return array_merge([$args[0]], explode(' ', $args[1]), array_slice($args, 3));
}
return $args;
}
/**
* Determine if the specified argument is a path to a shebang script.
* If so, load it.
*
* @param string $filepath
* File to check.
*
* @return bool
* Returns TRUE if shebang script was processed.
*/
protected function isShebangFile($filepath)
{
// Avoid trying to call $filepath on remote URLs
if ((strpos($filepath, '://') !== false) && (substr($filepath, 0, 7) != 'file://')) {
return false;
}
if (!is_file($filepath)) {
return false;
}
$fp = fopen($filepath, "r");
if ($fp === false) {
return false;
}
$line = fgets($fp);
$result = $this->isShebangLine($line);
if ($result) {
while ($line = fgets($fp)) {
$line = trim($line);
if ($line == '<?php') {
$script = stream_get_contents($fp);
if (preg_match('#^class *([^ ]+)#m', $script, $matches)) {
$this->roboClass = $matches[1];
eval($script);
$result = true;
}
}
}
}
fclose($fp);
return $result;
}
/**
* Test to see if the provided line is a robo 'shebang' line.
*
* @param string $line
*
* @return bool
*/
protected function isShebangLine($line)
{
return ((substr($line, 0, 2) == '#!') && (strstr($line, 'robo') !== false));
}
/**
* Check for Robo-specific arguments such as --load-from, process them,
* and remove them from the array. We have to process --load-from before
* we set up Symfony Console.
*
* @param array $argv
*
* @return array
*/
protected function processRoboOptions($argv)
{
// loading from other directory
$pos = $this->arraySearchBeginsWith('--load-from', $argv) ?: array_search('-f', $argv);
if ($pos === false) {
return $argv;
}
$passThru = array_search('--', $argv);
if (($passThru !== false) && ($passThru < $pos)) {
return $argv;
}
if (substr($argv[$pos], 0, 12) == '--load-from=') {
$this->dir = substr($argv[$pos], 12);
} elseif (isset($argv[$pos + 1])) {
$this->dir = $argv[$pos + 1];
unset($argv[$pos + 1]);
}
unset($argv[$pos]);
// Make adjustments if '--load-from' points at a file.
if (is_file($this->dir) || (substr($this->dir, -4) == '.php')) {
$this->roboFile = basename($this->dir);
$this->dir = dirname($this->dir);
$className = basename($this->roboFile, '.php');
if ($className != $this->roboFile) {
$this->roboClass = $className;
}
}
// Convert directory to a real path, but only if the
// path exists. We do not want to lose the original
// directory if the user supplied a bad value.
$realDir = realpath($this->dir);
if ($realDir) {
chdir($realDir);
$this->dir = $realDir;
}
return $argv;
}
/**
* @param string $needle
* @param string[] $haystack
*
* @return bool|int
*/
protected function arraySearchBeginsWith($needle, $haystack)
{
for ($i = 0; $i < count($haystack); ++$i) {
if (substr($haystack[$i], 0, strlen($needle)) == $needle) {
return $i;
}
}
return false;
}
public function shutdown()
{
$error = error_get_last();
if (!is_array($error)) {
return;
}
$this->writeln(sprintf("<error>ERROR: %s \nin %s:%d\n</error>", $error['message'], $error['file'], $error['line']));
}
/**
* This is just a proxy error handler that checks the current error_reporting level.
* In case error_reporting is disabled the error is marked as handled, otherwise
* the normal internal error handling resumes.
*
* @return bool
*/
public function handleError()
{
if (error_reporting() === 0) {
return true;
}
return false;
}
/**
* @return string
*/
public function getSelfUpdateRepository()
{
return $this->selfUpdateRepository;
}
/**
* @param $selfUpdateRepository
*
* @return $this
*/
public function setSelfUpdateRepository($selfUpdateRepository)
{
$this->selfUpdateRepository = $selfUpdateRepository;
return $this;
}
/**
* @param string $configFilename
*
* @return $this
*/
public function setConfigurationFilename($configFilename)
{
$this->configFilename = $configFilename;
return $this;
}
/**
* @param string $envConfigPrefix
*
* @return $this
*/
public function setEnvConfigPrefix($envConfigPrefix)
{
$this->envConfigPrefix = $envConfigPrefix;
return $this;
}
/**
* @param \Composer\Autoload\ClassLoader $classLoader
*
* @return $this
*/
public function setClassLoader(ClassLoader $classLoader)
{
$this->classLoader = $classLoader;
return $this;
}
/**
* @param string $relativeNamespace
*
* @return $this
*/
public function setRelativePluginNamespace($relativeNamespace)
{
$this->relativePluginNamespace = $relativeNamespace;
return $this;
}
}
#!/usr/bin/env php
<?php
/**
* If we're running from phar load the phar autoload,
* else let the script 'robo' search for the autoloader.
*/
// Hack: \Phar::running() cannot be used reliably here to determine
// if we are running as a phar or not (works when phar is built with
// box, but does not work when phar is built with the Robo phar task.)
// We will use __FILE__ to determine our phar path; however, we cannot
// distinguish whether a __FILE__ of "/path/robo" is this file, or a
// 'robo.phar' that has been renamed to 'robo'. We will use the file
// size to differentiate.
// Recommendation: Use box to build your phar. See https://github.com/g1a/starter
$isPhar = (filesize(__FILE__) > 500000);
// Non-phar autoloader paths
$candidates = [
__DIR__.'/vendor/autoload.php',
__DIR__.'/../../autoload.php',
];
// Use our phar alias path
if ($isPhar) {
array_unshift($candidates, 'phar://robo.phar/vendor/autoload.php');
}
$autoloaderPath = false;
foreach ($candidates as $candidate) {
if (file_exists($candidate)) {
$autoloaderPath = $candidate;
break;
}
}
if (!$autoloaderPath) {
die("Could not find autoloader. Run 'composer install'.");
}
$classLoader = require $autoloaderPath;
$configFilePath = getenv('ROBO_CONFIG') ?: getenv('HOME') . '/.robo/robo.yml';
$runner = new \Robo\Runner();
$runner
->setRelativePluginNamespace('Robo\Plugin')
->setSelfUpdateRepository('consolidation/robo')
->setConfigurationFilename($configFilePath)
->setEnvConfigPrefix('ROBO')
->setClassLoader($classLoader);
$statusCode = $runner->execute($_SERVER['argv']);
exit($statusCode);
[ch~Œü?/'çSpmX¤8RGBMB